<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Gerard Rodes | blog — English</title>
    <link>https://blog.grod.es/</link>
    <description>Technical notes by Gerard Rodes.</description>
    <language>en</language>
    <atom:link href="https://blog.grod.es/en/feed.xml" rel="self" type="application/rss+xml"></atom:link>
    <atom:link href="https://blog.grod.es/feed.xml" rel="alternate" type="application/rss+xml" title="Gerard Rodes | blog"></atom:link>
    <atom:link href="https://blog.grod.es/en/feed.xml" rel="alternate" type="application/rss+xml" hreflang="en" title="Gerard Rodes | blog — English"></atom:link>
    <atom:link href="https://blog.grod.es/es/feed.xml" rel="alternate" type="application/rss+xml" hreflang="es" title="Gerard Rodes | blog — Castellano"></atom:link>
    <lastBuildDate>Thu, 04 Jun 2026 00:00:00 +0000</lastBuildDate>
    <item>
      <title>I have not written a line of code in five months</title>
      <link>https://blog.grod.es/i-have-not-written-a-line-of-code-in-five-months?utm_medium=feed&amp;utm_source=rss</link>
      <guid isPermaLink="true">https://blog.grod.es/i-have-not-written-a-line-of-code-in-five-months</guid>
      <pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate>
      <description>Some honest notes after several months building software with LLM agents: what works, what breaks, and why architecture, tests, context, and taste matter more than ever.</description>
      <dc:language>en</dc:language>
      <atom:link href="https://blog.grod.es/i-have-not-written-a-line-of-code-in-five-months" rel="alternate" type="text/html" hreflang="en" title="English"></atom:link>
      <atom:link href="https://blog.grod.es/i-have-not-written-a-line-of-code-in-five-months" rel="alternate" type="text/html" hreflang="x-default" title="Default version"></atom:link>
      <content:encoded><![CDATA[<p>During the last five months, I think I have barely written a line of code by hand, and I say this for good and for bad, because using AI for programming now feels obviously faster in many cases, to the point where manual coding can feel a bit absurd sometimes. You explain the intention, guide the architecture, review the output, ask for changes, ask for tests, ask for a refactor, and suddenly something that would have taken days starts taking hours.</p>
<p>At the same time, the engineering work has not disappeared; it has moved somewhere else. You are still responsible for the code. You still need architecture, tests, taste, and the ability to understand what is being produced. I would even say all of that matters more now, because generating a lot of code very quickly also means generating a lot of garbage very quickly if you don&#39;t know how to guide the process.</p>
<p>I am not trying to write a grand theory of AI here, only my honest view after using LLMs seriously for work projects and personal projects, and after pushing them further than I expected to push them.</p>
<h2 id="smart-and-dumb-at-the-same-time"><a class="heading-anchor" href="#smart-and-dumb-at-the-same-time">Smart and dumb at the same time</a></h2>
<p>The most frustrating part for me is how unstable the experience feels. You can ask a model to reason about a complex codebase, propose a structure, connect ideas that you had not connected, and sometimes it will one-shot something better than what you had in mind. Five minutes later, you ask it to do one tiny deterministic thing and it fails in the stupidest possible way.</p>
<p>I am not exaggerating here; for me, this has become the normal experience. It feels like working with something that can be one or two levels smarter than you in one moment, and completely unable to follow a simple instruction in the next one. It can design a decent architecture for a service, and then get stuck doing workaround after workaround for a small bug that you would have fixed manually in ten minutes.</p>
<p>For that reason, I don&#39;t like the simple question of whether AI is good or bad for coding; the answer depends too much on which part of the work you are looking at. In some dimensions it is very good, and in others it is absurdly bad. If you want to use it seriously, you need to learn where those borders are.</p>
<h2 id="prompting-is-not-programming"><a class="heading-anchor" href="#prompting-is-not-programming">Prompting is not programming</a></h2>
<p>One of the things I built during this period was an incident investigation bot. The idea was simple: given an alert, the bot should inspect metrics, logs, traces, dashboards, and downstream dependencies until it reaches a useful hypothesis about the root cause.</p>
<p>My main problem with LLMs in this kind of task is laziness. They tend to stop at the first plausible explanation. If an endpoint is failing because a downstream dependency is failing, the model will often say: &#34;the downstream dependency is failing&#34;, and stop there. The problem is that a downstream failure is usually only the next node in the investigation, not the actual root cause.</p>
<p>A human keeps going to the downstream service, checks its logs, checks its traces, checks whether it has another downstream dependency, and keeps following the error until reaching a dead end, or at least until reaching the deepest useful explanation.</p>
<p>So I tried to encode that into the prompt. I described the investigation as a graph. Services are nodes. Signals are edges. A log, a metric spike, a span error, a dependency failure: all of those are edges that let you move to another node. The bot has to keep traversing the graph until the path stops being useful.</p>
<p>The bot got much better after that, although new problems appeared immediately. Sometimes it would over-traverse. Sometimes it would treat the deepest branch as the root cause, even when another branch was more directly related to the user impact. Sometimes fixing one failure mode in the prompt would break another one.</p>
<p>The strange thing with prompts is that they are not code and they are not deterministic. You cannot write a prompt the same way you write a function and expect the same input to always produce the same behavior. You are not programming a machine in the classical sense. You are shaping the behavior of a probability machine, and the more instructions you add, the more strange interactions you can create.</p>
<p>I don&#39;t think this problem is solved. If it were solved, prompt injection and jailbreaks would not exist in the way they exist today. A longer prompt is not necessarily better. Often, what you really want is a prompt that communicates the final objective clearly enough for the model to keep rediscovering the right behavior by itself. Finding that sentence is very hard.</p>
<h2 id="context-is-everything"><a class="heading-anchor" href="#context-is-everything">Context is everything</a></h2>
<p>The next thing I learned, or maybe relearned, was that context matters more than the prompt itself. The model answers your last message through everything around it: its weights, your prompt, the system instructions, and the whole current context of the conversation.</p>
<p>When the context is clean, it behaves better. When the context is full of trash, half-finished attempts, wrong assumptions, noisy tool outputs, and stale conclusions, it degrades.</p>
<p>Todo lists are one of the simplest ways I have found to control the behavior of an agent. The UI is cute, but the important part is the control mechanism behind it.</p>
<p>When I want an agent to do a big task, I don&#39;t just say &#34;do it&#34;. I first ask it to plan with me, convert that plan into exhaustive checklist items, mark each item as done while it works, and continue until there are no checkboxes left. Small trick, big difference in behavior. The checklist prevents the model from stopping early, gives the agent a local memory of what it is doing, and gives me a way to inspect whether the plan is sane before the agent starts producing code.</p>
<p>For very long sessions, the plan becomes even more important. The model needs to compact its context from time to time, and before doing that it should write back the important findings, decisions, open questions, and completed work into the plan. After compaction or restart, the plan becomes the brain of the session. The agent wakes up again, reads the plan, and remembers who it is. I know this sounds ridiculous, but after using it for long-running tasks it becomes very clear why it works.</p>
<h2 id="tools-are-a-way-of-protecting-context"><a class="heading-anchor" href="#tools-are-a-way-of-protecting-context">Tools are a way of protecting context</a></h2>
<p>The context window is precious, and filling it with garbage makes everything worse. Tools matter so much for exactly this reason.</p>
<p>If an agent needs to rename a symbol, I don&#39;t want it reading the whole file as text, generating the whole file again, and hoping that the patch applies. I want it to use an LSP, the same way my editor does: ask for the symbol, rename it, inspect references, and move on.</p>
<p>Something similar happens with MCPs. I know MCP sounds new, but in my head MCP is basically SOAP for LLMs. SOAP was this old way of exposing self-documented APIs through XML. MCP is a way of exposing tools to an LLM with enough structure that the model can discover what exists, understand the input schema, and call the right method.</p>
<p>For me, the useful part of MCP is very boring: the model can interact with the world without needing the whole world copied into the prompt. The most useful tools are often tiny utilities built specifically to reduce context waste, not huge integrations. For the incident bot, traces were a good example. A trace can be massive: thousands of lines of JSON, spans, attributes, metadata, timestamps, and repeated fields. But for an investigation, the first useful thing is often much smaller: which spans are in error, how the error propagated, and which service boundaries are involved.</p>
<p>So I built a small utility that takes the raw trace JSON and produces a compact ASCII tree of the error path. Instead of giving the model 12,000 lines of JSON, I give it a few lines that show the shape of the failure. If it needs more detail, it can inspect the original span later.</p>
<p>That utility is boring, but it made a huge difference, because good agent tooling is often about the shape of the information more than about the amount of information.</p>
<h2 id="the-model-can-raise-your-level-but-it-cannot-replace-your-taste"><a class="heading-anchor" href="#the-model-can-raise-your-level-but-it-cannot-replace-your-taste">The model can raise your level, but it cannot replace your taste</a></h2>
<p>My current mental model is that an LLM can raise you one or two levels in an area. If you are a 2 out of 10 in something, maybe with an LLM you can produce something that looks like a 3 or a 4.</p>
<p>The usefulness has a dangerous side: if you are bad at judging the result, you will still produce bad work, only now it will look better to you than what you could have produced alone.</p>
<p>If you don&#39;t know Terraform, the model can generate Terraform for you. But you still need to know enough to recognize duplication, bad abstractions, broken state assumptions, weird module boundaries, and hidden operational risk. If you don&#39;t know Go, the model can generate Go for you. But you still need to know enough to recognize bad error handling, confused ownership, unnecessary interfaces, leaky abstractions, and tests that don&#39;t actually test anything.</p>
<p>The model does not remove the need for expertise; it amplifies the expertise you already have, and I started using multiple conversations against each other because of this. One agent writes the code. Another agent reviews it harshly. Another one checks it against my guidelines. Another one tries to simplify it. Sometimes I ask the same model to behave as if the previous output was produced by a very dumb AI and it has to find everything wrong with it.</p>
<p>Surprisingly, this works quite well. I don&#39;t think the model becomes more truthful because of this. I think the role and the context change the distribution of the answer. If you ask it &#34;is this good?&#34;, it will often be too nice. If you ask it for a harsh code review and tell it to assume the code was generated by a lazy AI, it finds much more. A bit stupid, but useful.</p>
<h2 id="speed-creates-a-new-problem-understanding"><a class="heading-anchor" href="#speed-creates-a-new-problem-understanding">Speed creates a new problem: understanding</a></h2>
<p>The first versions of some of my projects appeared absurdly fast. In the past, speed was mostly limited by typing, searching, boilerplate, and the time needed to discover APIs. With LLMs, that part almost disappears.</p>
<p>The bottleneck moves to understanding what now exists. You can wake up with thousands of lines of code that were generated while you were sleeping, which is impressive and dangerous for exactly the same reason.</p>
<p>If something breaks, your first instinct may be to ask the agent for one more fix, and after that another one, and after that another one. At some point you are not engineering anymore. You are prompting around a codebase you don&#39;t understand.</p>
<p>A strong test suite saves you there. If you use AI to move faster, you should also use AI to create more tests than you would have written manually. Every bug found should become a test. Every weird piece of business logic should have comments and regression coverage. Every refactor should be backed by something stronger than &#34;the agent says it is fine&#34;.</p>
<p>Refactoring helps with the same problem. An LLM will usually not stop and say: &#34;dude, this code is becoming horrible, let&#39;s clean it up&#34;. If you ask it to add a feature, it will add the feature on top of the current state. It will try to reach the end result, because that is what you asked for.</p>
<p>So you need to explicitly ask for refactoring passes. Ask it to find duplication. Ask it where responsibilities are leaking. Ask it which abstractions are fake. Ask it what can be deleted. Ask it where the tests are too coupled to implementation details.</p>
<p>For me, one of the best uses of AI is doing the boring cleanup that everybody knows should happen and nobody wants to spend a week doing. Before AI, large refactors often died because they were too annoying. After AI, the annoying part is much smaller. The refactor still needs review and tests, of course, although the cost of attempting it is lower.</p>
<h2 id="incident-investigation-is-mostly-graph-traversal"><a class="heading-anchor" href="#incident-investigation-is-mostly-graph-traversal">Incident investigation is mostly graph traversal</a></h2>
<p>The incident bot made me realize something obvious: when we investigate production issues, we are usually navigating a graph.</p>
<p>You start with an alert. That alert points to a service, an endpoint, a queue, a database, or some business metric. In practice, that becomes your first node.</p>
<p>From there, you look for signals: logs, metrics, traces, deployment events, saturation, dependency errors, feature flags, configuration changes. Each signal gives you an edge to another node.</p>
<p>You follow the edge and now you are in another service, another dependency, another metric, another part of the system. You repeat the process until you reach something that explains the impact well enough.</p>
<p>Thinking like this helped me write a better prompt, although it also made something else painfully clear: we only need this kind of bot because our observability is not good enough.</p>
<p>If traces were complete, consistent, and connected across services, many investigations would be much easier. You would open the trace, follow the failing span, and see the path. But in real systems, traces are not always connected. Logs don&#39;t always have the right attributes. Metrics don&#39;t always share the same labels. Dashboards are sometimes empty, misleading, duplicated, or owned by nobody. Some services follow OpenTelemetry conventions. Others don&#39;t. Some dependencies are visible. Others disappear at the boundary.</p>
<p>The bot has to compose a picture from broken pieces, which can be useful, but it is still a symptom. An AI incident bot is only as good as your telemetry. If the telemetry lies, the bot will reason from lies. If the traces are disconnected, the bot will invent bridges or stop too early. If ownership is unclear, the bot will not magically know who should act. If alerts are noisy, it will waste time on noise. A better bot can help, but the real fix is better observability.</p>
<h2 id="token-usage-is-part-of-the-design"><a class="heading-anchor" href="#token-usage-is-part-of-the-design">Token usage is part of the design</a></h2>
<p>Long conversations are expensive, not only in money but in attention. Every time you send a new message, the system has to process the relevant context again. Caching helps, but cache can break. Tool outputs, timestamps, changed messages, and client behavior can all make the provider recompute more than you expect.</p>
<p>Shorter sessions sometimes work better because long sessions, while useful for some work, also accumulate dirt.</p>
<p>For small tasks, I prefer starting fresh. For big tasks, keep a plan file, compact aggressively, and make sure the durable memory is outside the chat. The source of truth should be the plan, the tests, the code, and the documentation, not the chat itself.</p>
<p>The model can talk to itself for a while, and that can improve the answer. Reasoning tokens exist because of that. But there is a sweet spot: more thinking does not always improve the result, and faster answers are not automatically better either.</p>
<p>The useful question is less &#34;how do I make the model faster?&#34; and more &#34;what is the minimum reasoning path that still gives me a good answer for this task?&#34;</p>
<p>Sometimes you want the model to think deeply; other times you want a small deterministic edit and nothing else. The skill is knowing which mode you are asking for.</p>
<h2 id="so-should-we-use-ai-to-code"><a class="heading-anchor" href="#so-should-we-use-ai-to-code">So, should we use AI to code?</a></h2>
<p>Yes, but not as a replacement for engineering. Use it to move faster. Use it to explore. Use it to generate tests. Use it to refactor boring code. Use it to critique your own work. Use it to build small tools that would otherwise not exist. Use it to turn vague ideas into prototypes. Use it to make yourself more ambitious.</p>
<p>Just don&#39;t use it as an excuse to stop understanding. The worst version of AI-assisted programming is a developer prompting changes into a codebase they no longer understand, trusting green tests that don&#39;t mean much, and accumulating abstractions that nobody would have written by hand.</p>
<p>The version that makes sense to me is the one where the developer still owns the architecture, still owns the trade-offs, still reads the important parts, still writes or reviews the tests, still simplifies, still deletes, still asks whether the thing should exist at all.</p>
<p>The code may be generated by the model, but the responsibility is still yours. Maybe I have not written a line of code in five months, but I have been coding in another place: in the architecture, in the prompts, in the tools, in the tests, in the refactors, in the shape of the context, and in the taste required to decide when the machine is right and when it is just confidently producing slop.</p>
<h2 id="end-note"><a class="heading-anchor" href="#end-note">End note</a></h2>
<aside class="callout callout-note">
This article is based on the transcript of a presentation I gave on June 4, 2026.
</aside>
<p>I just wanted to add that, depending on the day, I feel bullish or bearish on AI: <a href="https://notas.grod.es/the-rain-spell">https://notas.grod.es/the-rain-spell</a>. Some days I feel excited about the future we are building with it, and other days I feel honestly depressed about it. I am still not convinced about what that future will look like, or even whether at some point I will decide to reject AI completely and go back to writing all code by myself.</p>
<hr/><aside class="discussion-section"><h2>Discussion</h2><ul><li><a href="https://news.ycombinator.com/item?id=48608134"><img src="https://blog.grod.es/icons/hacker-news.ico?v=402519a37fed" alt=""/> Hacker News</a></li></ul></aside>]]></content:encoded>
    </item>
    <item>
      <title>PostgreSQL autovacuum can take an AccessExclusiveLock when truncating tables</title>
      <link>https://blog.grod.es/postgresql-vacuum-might-acquire-an-access-exclusive-lock?utm_medium=feed&amp;utm_source=rss</link>
      <guid isPermaLink="true">https://blog.grod.es/postgresql-vacuum-might-acquire-an-access-exclusive-lock</guid>
      <pubDate>Thu, 20 Jun 2024 00:00:00 +0000</pubDate>
      <description>PostgreSQL VACUUM and autovacuum can briefly take an AccessExclusiveLock when truncating empty pages at the end of a table. Here is why it happens and how vacuum_truncate=false avoids it.</description>
      <dc:language>en</dc:language>
      <atom:link href="https://blog.grod.es/postgresql-vacuum-might-acquire-an-access-exclusive-lock" rel="alternate" type="text/html" hreflang="en" title="English"></atom:link>
      <atom:link href="https://blog.grod.es/postgresql-vacuum-might-acquire-an-access-exclusive-lock" rel="alternate" type="text/html" hreflang="x-default" title="Default version"></atom:link>
      <content:encoded><![CDATA[<p>In this post I will explain how <code>VACUUM</code> might obtain an <code>AccessExclusiveLock</code> on a table with a very specific layout due to truncation maintenance operations. Combining that behavior with the unexpected nature of AutoVacuum can create undesirable scenarios at the worst times.</p>
<h2 id="why-autovacuum-can-unexpectedly-take-this-lock"><a class="heading-anchor" href="#why-autovacuum-can-unexpectedly-take-this-lock">Why autovacuum can unexpectedly take this lock</a></h2>
<p>PostgreSQL autovacuum is a background process which performs <a href="https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-BASICS">maintenance operations</a> on database tables. You can think of it as a job that starts running based on some heuristic (which can be configured) and executes the <code>VACUUM</code> command on a table.
My understanding was that <code>VACUUM</code> was safe to run online, that it wouldn’t cause any outage in production as it doesn’t acquire an <code>AccessExclusiveLock</code>. I frequently check <a href="https://pglocks.org/?pgcommand=VACUUM">this website</a> to learn which locks are acquired by each PostgreSQL command. And it shows that plain <code>VACUUM</code> normally obtains a <code>ShareUpdateExclusiveLock</code>.</p>
<p>But there is one specific case where <code>VACUUM</code> (and consequently the autovacuum process too) will acquire an <code>AccessExclusiveLock</code>:</p>
<blockquote>
<p>The standard form of VACUUM removes dead row versions in tables and indexes and marks the space available for future reuse. However, <strong>it will not return the space to the operating system, except in the special case where one or more pages at the end of a table become entirely free and an exclusive table lock can be easily obtained.</strong> <a href="https://www.postgresql.org/docs/current/routine-vacuuming.html#:~:text=The%20standard%20form%20of%20VACUUM%20removes,table%20lock%20can%20be%20easily%20obtained">docs ref</a></p>
</blockquote>
<h2 id="when-vacuum-truncates-empty-pages"><a class="heading-anchor" href="#when-vacuum-truncates-empty-pages">When VACUUM truncates empty pages</a></h2>
<p>If you are deleting old records from a table, leaving empty pages (pages with only dead tuples) at the end of your table (physical position in disk), <code>VACUUM</code> will obtain an <code>AccessExclusiveLock</code> on that table, if it can obtain it immediately. It will then truncate those empty pages to free up some disk space.</p>
<figure><img src="https://blog.grod.es/images/unexpected_vacuum_outage__autovacuum_truncation.png" alt="AutoVacuum executing VACUUM in a table with empty pages"/><figcaption>AutoVacuum executing VACUUM in a table with empty pages</figcaption></figure>
<p>This is exactly what happened to me as we were running a job that was migrating old records to cold storage and deleting them afterward from the table. This generated the perfect scenario for AutoVacuum to lock the table for a while and cause a production outage.</p>
<h2 id="solutions"><a class="heading-anchor" href="#solutions">Solutions</a></h2>
<h3 id="preventing-the-lock-with-vacuum-truncatefalse"><a class="heading-anchor" href="#preventing-the-lock-with-vacuum-truncatefalse">Preventing the lock with vacuum_truncate=false</a></h3>
<p>Since PostgreSQL 12, it is possible to configure the <code>VACUUM</code> command to disable the truncation behavior by defining the following setting on a per-table basis:</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">ALTER</span><span class="chroma-w"> </span><span class="chroma-k">TABLE</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">my_table</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">SET</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">vacuum_truncate</span><span class="chroma-o">=</span><span class="chroma-k">false</span><span class="chroma-p">,</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-k">toast</span><span class="chroma-p">.</span><span class="chroma-n">vacuum_truncate</span><span class="chroma-o">=</span><span class="chroma-k">false</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>If you are running <code>VACUUM</code> manually instead of AutoVacuum, you can parameterize the command itself:</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">VACUUM</span><span class="chroma-w"> </span><span class="chroma-p">(</span><span class="chroma-k">TRUNCATE</span><span class="chroma-w"> </span><span class="chroma-k">FALSE</span><span class="chroma-p">)</span><span class="chroma-w"> </span><span class="chroma-n">my_table</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>Although you will want to free up that disk space at some point. If you are still afraid of  <code>AccessExclusiveLock</code> you can execute an online (concurrent) table rewrite with <a href="https://github.com/reorg/pg_repack">pg_repack</a>.</p>
<h3 id="use-truncate-if-your-data-model-allows-it"><a class="heading-anchor" href="#use-truncate-if-your-data-model-allows-it">Use <code>TRUNCATE</code>, if your data model allows it</a></h3>
<p>Another solution is to execute the <a href="https://www.postgresql.org/docs/current/sql-truncate.html"><code>TRUNCATE</code></a> command (instead of <code>DELETE</code>) when removing the data you are migrating. This will acquire an <code>AccessExclusiveLock</code> and free up disk space immediately.</p>
<p>This is not a drop-in replacement for <code>DELETE</code>, though. <code>TRUNCATE</code> removes all rows from a table (or from a partition, if you are truncating partitions), so this only makes sense if your data model lets you isolate old data cleanly. For example, old data could live in partitions, and then you can truncate whole old partitions instead of deleting millions of rows from the middle of a table.</p>
<p>I haven’t tried it yet in this exact setup, but it should be very fast to execute when the operation is scoped to an old partition or a temporary/staging table. The <code>AccessExclusiveLock</code> still exists, but it is explicit, predictable, and hopefully short-lived. In my case <code>VACUUM</code> started to truncate millions of rows as a side effect of a normal maintenance operation, which seems to have locked the table long enough to trigger our on-call monitors.</p>
<h2 id="community-notes"><a class="heading-anchor" href="#community-notes">Community notes</a></h2>
<p>It seems like this truncation shouldn&#39;t be a big problem in many setups, and some people have pointed out that this post might be exaggerated or too alarmist. However, there have been other responses explaining that when streaming replicas are in place, they might exacerbate the issue. The standby has to replay what happened on the primary. If replaying the truncation conflicts with queries running on the standby, it has to either delay WAL replay or cancel the conflicting queries.</p>
<blockquote>
<p>Except if you have streaming replicas. The problem then is that replicas MUST replay the truncation, so either replication blocks or the replica needs to kill any blocking queries. Hot Standby Feedback does NOT do anything to alleviate this either.</p>
<p><a href="https://www.linkedin.com/feed/update/urn:li:activity:7213568035670949890?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7213568035670949890%2C7213585226332012545%29&amp;replyUrn=urn%3Ali%3Acomment%3A%28activity%3A7213568035670949890%2C7213592590317682689%29&amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287213585226332012545%2Curn%3Ali%3Aactivity%3A7213568035670949890%29&amp;dashReplyUrn=urn%3Ali%3Afsd_comment%3A%287213592590317682689%2Curn%3Ali%3Aactivity%3A7213568035670949890%29" target="_blank">Jim Nasby @ LinkedIn</a></p>
</blockquote>
<blockquote>
<p>When I encountered this years ago it was exacerbated by the use of read replicas. The vacuum process has no way of knowing that it&#39;s blocking a query on the read replica.</p>
<p><a href="https://www.reddit.com/r/PostgreSQL/comments/1dndopd/comment/la44ibk/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button" target="_blank">ICThat @ Reddit</a></p>
</blockquote>
<hr/>
<p>Once you know about this particularity of <code>VACUUM</code>, it is trivial to avoid it in the future. Interestingly enough, I had never heard about this specific behavior before, and my understanding was that <code>VACUUM</code> was &#34;safe to run but doesn’t recover disk space&#34; and <code>VACUUM FULL</code> was the heavy, dangerous, full-locking, version of it.</p>
<p>Turns out <code>VACUUM</code> can be dangerous too.</p>
<hr/><aside class="discussion-section"><h2>Discussion</h2><ul><li><a href="https://www.reddit.com/r/PostgreSQL/comments/1dndopd/postgresqls_vacuum_might_acquire_an/"><img src="https://blog.grod.es/icons/reddit.png?v=925fe993dba7" alt=""/> Reddit</a></li><li><a href="https://www.linkedin.com/feed/update/urn:li:activity:7213568035670949890/"><img src="https://blog.grod.es/icons/linkedin.ico?v=19b079c09197" alt=""/> LinkedIn</a></li></ul></aside>]]></content:encoded>
    </item>
    <item>
      <title>Single-threaded async Rust</title>
      <link>https://blog.grod.es/single-threaded-async-rust?utm_medium=feed&amp;utm_source=rss</link>
      <guid isPermaLink="true">https://blog.grod.es/single-threaded-async-rust</guid>
      <pubDate>Sun, 24 Jul 2022 00:00:00 +0000</pubDate>
      <description>A small exploration of Rust futures, executors, and why async Rust can run perfectly well on a single thread when the runtime is built that way.</description>
      <dc:language>en</dc:language>
      <atom:link href="https://blog.grod.es/single-threaded-async-rust" rel="alternate" type="text/html" hreflang="en" title="English"></atom:link>
      <atom:link href="https://blog.grod.es/single-threaded-async-rust" rel="alternate" type="text/html" hreflang="x-default" title="Default version"></atom:link>
      <content:encoded><![CDATA[<p>This week I wanted to learn Rust for the second time, so I read some of the pages
on the Rust book, once I&#39;ve reached async-related topics I noticed that it is a bit
different from what other languages do. I come from a background in Go, JS, and Python.</p>
<p>I thought that writing yet another terminal utility with Rust that does network requests
would be a good introduction to Rust async programming.</p>
<p>Rust&#39;s standard library provides the <code>Future</code> trait to represent asynchronous computations, but it does not provide an async runtime.
Futures do not execute by themselves and you need something to poll them to further advance their execution.
That&#39;s what async runtimes do and, for the moment, the std lib does not provide one. It has been
left to the community to implement it. I&#39;ve chosen <a href="https://tokio.rs/">tokio</a> since it is the most used one and I have
no preference or idea about what I need.</p>
<p>But I had one thing clear, the only things that my TUI has to do is:</p>
<ol>
<li>Read from stdin.</li>
<li>Do a network request.</li>
<li>Write to stdout.</li>
</ol>
<p>It is little work, very simple, the only extra work we could add here is to take into account that
once you receive a response from the request you have to deserialize the response into memory.
Since Rust is known for its performance I wanted to write this TUI around the idea of limiting it to
a single thread. I don&#39;t want the tokio runtime to have to handle thread synchronization, for this simple
task a single thread should be more than enough and still provide a non-blocking interface to the user.
Tokio provides a macro that you can use on your functions that unwraps into a runtime initialization:
<a href="https://docs.rs/tokio/latest/tokio/attr.main.html#usage"><code>#[tokio::main]</code></a></p>
<p>And it has variants to specify things like the number of threads to use, or like in my case, if you want to
only use the current thread:
<code>#[tokio::main(flavor = &#34;current_thread&#34;)]</code></p>
<p>Small caveat: using the <code>current_thread</code> runtime means that Tokio&#39;s scheduler runs tasks on the current thread. It does not necessarily mean that the entire process will never use helper threads. For example, depending on how you read from stdin, Tokio may still use blocking work behind the scenes.</p>
<p>I wanted to first set up a test playground to get a grasp of really how Rust and tokio work to see how I could
write an app with them.</p>
<p>This piece of code just defines an imaginary UI that has a <code>draw</code> method that is very demanding
and panics if has elapsed more than 200ms since the last time that it has been called.</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">use</span><span class="chroma-w"> </span><span class="chroma-n">std</span>::<span class="chroma-n">time</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">struct</span> <span class="chroma-nc">UI</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">last_draw</span>: <span class="chroma-nc">time</span>::<span class="chroma-n">Instant</span><span class="chroma-p">,</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">impl</span><span class="chroma-w"> </span><span class="chroma-no">UI</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-k">fn</span> <span class="chroma-nf">default</span><span class="chroma-p">()</span><span class="chroma-w"> </span>-&gt; <span class="chroma-nc">UI</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-no">UI</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-n">last_draw</span>: <span class="chroma-nc">time</span>::<span class="chroma-n">Instant</span>::<span class="chroma-n">now</span><span class="chroma-p">(),</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-k">fn</span> <span class="chroma-nf">draw</span><span class="chroma-w"> </span><span class="chroma-p">(</span><span class="chroma-o">&amp;</span><span class="chroma-k">mut</span><span class="chroma-w"> </span><span class="chroma-bp">self</span><span class="chroma-p">)</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-kd">let</span><span class="chroma-w"> </span><span class="chroma-n">now</span><span class="chroma-w"> </span><span class="chroma-o">=</span><span class="chroma-w"> </span><span class="chroma-n">time</span>::<span class="chroma-n">Instant</span>::<span class="chroma-n">now</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-fm">assert!</span><span class="chroma-p">(</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-n">now</span><span class="chroma-p">.</span><span class="chroma-n">duration_since</span><span class="chroma-p">(</span><span class="chroma-bp">self</span><span class="chroma-p">.</span><span class="chroma-n">last_draw</span><span class="chroma-p">).</span><span class="chroma-n">as_millis</span><span class="chroma-p">()</span><span class="chroma-w"> </span><span class="chroma-o">&lt;=</span><span class="chroma-w"> </span><span class="chroma-mi">200</span><span class="chroma-p">,</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-s">&#34;It has been too long since last draw!&#34;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-p">);</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-bp">self</span><span class="chroma-p">.</span><span class="chroma-n">last_draw</span><span class="chroma-w"> </span><span class="chroma-o">=</span><span class="chroma-w"> </span><span class="chroma-n">now</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>Then we define a method that will simulate the network request, it has 3 <code>sleep</code> calls of 100ms each,
so we will have to be sure that our <code>UI::draw</code> method gets called at least in the middle of some of these <code>sleep</code>.</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-k">fn</span> <span class="chroma-nf">network_request</span><span class="chroma-p">()</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-fm">println!</span><span class="chroma-p">(</span><span class="chroma-s">&#34;sending HTTP request...&#34;</span><span class="chroma-p">);</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">tokio</span>::<span class="chroma-n">time</span>::<span class="chroma-n">sleep</span><span class="chroma-p">(</span><span class="chroma-n">time</span>::<span class="chroma-n">Duration</span>::<span class="chroma-n">from_millis</span><span class="chroma-p">(</span><span class="chroma-mi">100</span><span class="chroma-p">)).</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-fm">println!</span><span class="chroma-p">(</span><span class="chroma-s">&#34;deserializing JSON...&#34;</span><span class="chroma-p">);</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">tokio</span>::<span class="chroma-n">time</span>::<span class="chroma-n">sleep</span><span class="chroma-p">(</span><span class="chroma-n">time</span>::<span class="chroma-n">Duration</span>::<span class="chroma-n">from_millis</span><span class="chroma-p">(</span><span class="chroma-mi">100</span><span class="chroma-p">)).</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-fm">println!</span><span class="chroma-p">(</span><span class="chroma-s">&#34;some more sleep...&#34;</span><span class="chroma-p">);</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">tokio</span>::<span class="chroma-n">time</span>::<span class="chroma-n">sleep</span><span class="chroma-p">(</span><span class="chroma-n">time</span>::<span class="chroma-n">Duration</span>::<span class="chroma-n">from_millis</span><span class="chroma-p">(</span><span class="chroma-mi">100</span><span class="chroma-p">)).</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>And in my first attempt, I failed shamefully. In my incorrect mental model, I thought that in some way, once
it reached one of the <code>await</code> within <code>network_request</code> it would somehow halt execution there and continue with the
next loop to keep calling <code>ui.draw()</code>.</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-cp">#[tokio::main(flavor = </span><span class="chroma-s">&#34;current_thread&#34;</span><span class="chroma-cp">)]</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-k">fn</span> <span class="chroma-nf">main</span><span class="chroma-p">()</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-kd">let</span><span class="chroma-w"> </span><span class="chroma-k">mut</span><span class="chroma-w"> </span><span class="chroma-n">ui</span><span class="chroma-w"> </span><span class="chroma-o">=</span><span class="chroma-w"> </span><span class="chroma-no">UI</span>::<span class="chroma-n">default</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-k">loop</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-n">ui</span><span class="chroma-p">.</span><span class="chroma-n">draw</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-n">network_request</span><span class="chroma-p">().</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>Now I see how blind I was, here the <code>async</code> primitive just allows us to write asynchronous logic
in a synchronous-looking way. By awaiting <code>network_request</code> directly inside the loop, I am waiting for that future to finish before drawing again. The sleeps themselves are non-blocking, but there is no other branch in this program that keeps drawing the UI while the request future is pending, so the UI is not drawn for ~300ms and it panics.</p>
<p>Because I wasn&#39;t understanding why it wasn&#39;t working I decided to look at what other TUIs were doing, but
<a href="https://www.google.com/search?q=%22current_thread%22+%22tui%22+filetype%3Ars">surprisingly</a> I haven&#39;t found
any Rust file containing &#34;current_thread&#34; and &#34;tui&#34; (I am writing the TUI with <a href="https://github.com/fdehau/tui-rs">tui-rs</a> and wanted to
see how to integrate it with single-threaded tokio).</p>
<p>But after reading a little bit I&#39;ve understood that I need to offer the tokio runtime a
chance to work on multiple tasks to intersperse them.</p>
<p>In the previous example, I was only driving a single future. But if we generate 2 async branches, one for the
interface and the other to handle requests, the tokio runtime will be able to run them concurrently.</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-cp">#[tokio::main(flavor = </span><span class="chroma-s">&#34;current_thread&#34;</span><span class="chroma-cp">)]</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-k">fn</span> <span class="chroma-nf">main</span><span class="chroma-p">()</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">tokio</span>::<span class="chroma-fm">join!</span><span class="chroma-p">(</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w"> </span><span class="chroma-c1">// interface
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-kd">let</span><span class="chroma-w"> </span><span class="chroma-k">mut</span><span class="chroma-w"> </span><span class="chroma-n">ui</span><span class="chroma-w"> </span><span class="chroma-o">=</span><span class="chroma-w"> </span><span class="chroma-no">UI</span>::<span class="chroma-n">default</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-k">loop</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">				</span><span class="chroma-n">ui</span><span class="chroma-p">.</span><span class="chroma-n">draw</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-p">},</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w"> </span><span class="chroma-c1">// network requests
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-k">loop</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">				</span><span class="chroma-n">network_request</span><span class="chroma-p">().</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">);</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>Here <code>tokio::join!</code> is running the provided async blocks and waiting for them to finish before exiting.
It is important to understand that <code>join!</code> is not spawning independent Tokio tasks here. It multiplexes those async branches inside the same task, which is exactly the kind of thing I wanted for this experiment: concurrency without parallelism.
Although the previous snippet doesn&#39;t work, since the interface loop is not asynchronous so it is not giving
a chance to the network task to execute, so we need a wait to &#34;turn it into async&#34; and stop it.</p>
<p>The first thing that came to my mind was just to use <code>tokio::time::sleep</code> (tokio provides async std lib alternatives) but
it seemed seedy to me, I just needed a semantic to specify that I want to stop the current execution of the task to run other
possible concurrent tasks.
I found <a href="https://docs.rs/tokio/latest/tokio/task/fn.yield_now.html"><code>tokio::task::yield_now</code></a> and it looked exactly what I was looking
for but the truth is that it didn&#39;t work for this experiment. The interface branch was in fact stopping, but the runtime was free to poll
the interface branch again instead of executing the <code>request</code> branch, so the behavior looked the same as not using it.
After reading the docs, this is not something you should build correctness on: Tokio does not guarantee the exact scheduling order after <code>yield_now</code>.
Then I also found <a href="https://docs.rs/tokio/latest/tokio/task/fn.consume_budget.html"><code>consume_budget</code></a> and It looked like a good candidate,
unfortunately, it didn&#39;t work either, anyhow it exists under an <code>unstable-feature</code> flag, and if you have to access unstable features when learning
a new tool it is probably because you are not approaching it in the way that it was intended.</p>
<p>I also tried <a href="https://docs.rs/tokio/latest/tokio/time/fn.interval.html"><code>tokio::time::interval</code></a> and tick events
to provide something like a refresh rate. This is probably the more semantic primitive for a UI refresh loop, but in my experiment
I still managed to miss the deadline sometimes and the <code>draw</code> call panicked because it was called too late. I probably had some blocking work
or some wrong assumption in my mental model somewhere.</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w"> </span><span class="chroma-c1">// interface
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-kd">let</span><span class="chroma-w"> </span><span class="chroma-k">mut</span><span class="chroma-w"> </span><span class="chroma-n">interval</span><span class="chroma-w"> </span><span class="chroma-o">=</span><span class="chroma-w"> </span><span class="chroma-n">tokio</span>::<span class="chroma-n">time</span>::<span class="chroma-n">interval</span><span class="chroma-p">(</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-n">time</span>::<span class="chroma-n">Duration</span>::<span class="chroma-n">from_millis</span><span class="chroma-p">(</span><span class="chroma-mi">100</span><span class="chroma-p">)</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">);</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-k">loop</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-n">interval</span><span class="chroma-p">.</span><span class="chroma-n">tick</span><span class="chroma-p">().</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-n">ui</span><span class="chroma-p">.</span><span class="chroma-n">draw</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>After feeling defeated I ended up including the async sleep call and everything worked</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w"> </span><span class="chroma-c1">// interface
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-k">loop</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-n">ui</span><span class="chroma-p">.</span><span class="chroma-n">draw</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-n">tokio</span>::<span class="chroma-n">time</span>::<span class="chroma-n">sleep</span><span class="chroma-p">(</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-n">std</span>::<span class="chroma-n">time</span>::<span class="chroma-n">Duration</span>::<span class="chroma-n">new</span><span class="chroma-p">(</span><span class="chroma-mi">0</span><span class="chroma-p">,</span><span class="chroma-w"> </span><span class="chroma-mi">0</span><span class="chroma-p">)</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-p">).</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w"> </span><span class="chroma-c1">// I am ugly but I do work.
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>Being a little more practical, both tasks will need to access the UI state, so I&#39;ll need to share
the UI instance between them, since Rust needs to guarantee safe memory access on compile
time, it is not that easy to share memory between different parts of your code for newcomers.</p>
<p>I&#39;ve found the <a href="https://github.com/usagi/rust-memory-container-cs">following repository</a> which contains
a very good decision tree to determine which memory primitive to use for a given case.</p>
<figure><img src="https://blog.grod.es/images/rust-memory-container-cs-3840x2160-dark-back.png" alt="Rust memory container cheat-sheet"/><figcaption>Rust memory container cheat-sheet</figcaption></figure>
<p>Encapsulating our UI instance in a <code>std::cell::RefCell</code> will do the trick and will allow us to access
it in both async branches and even mutate it. This works here because I am not spawning real independent Tokio tasks and I am not holding the mutable borrow across an <code>.await</code>. If I moved this to <code>tokio::spawn</code>, this would become a different problem and <code>RefCell</code> would not be enough by itself.</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-cp">#[tokio::main(flavor = </span><span class="chroma-s">&#34;current_thread&#34;</span><span class="chroma-cp">)]</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-k">fn</span> <span class="chroma-nf">main</span><span class="chroma-p">()</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-kd">let</span><span class="chroma-w"> </span><span class="chroma-n">ui</span><span class="chroma-w"> </span><span class="chroma-o">=</span><span class="chroma-w"> </span><span class="chroma-n">std</span>::<span class="chroma-n">cell</span>::<span class="chroma-n">RefCell</span>::<span class="chroma-n">new</span><span class="chroma-p">(</span><span class="chroma-no">UI</span>::<span class="chroma-n">default</span><span class="chroma-p">());</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-n">tokio</span>::<span class="chroma-fm">join!</span><span class="chroma-p">(</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-k">loop</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">				</span><span class="chroma-n">ui</span><span class="chroma-p">.</span><span class="chroma-n">borrow_mut</span><span class="chroma-p">().</span><span class="chroma-n">draw</span><span class="chroma-p">();</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">				</span><span class="chroma-n">tokio</span>::<span class="chroma-n">time</span>::<span class="chroma-n">sleep</span><span class="chroma-p">(</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">					</span><span class="chroma-n">time</span>::<span class="chroma-n">Duration</span>::<span class="chroma-n">new</span><span class="chroma-p">(</span><span class="chroma-mi">0</span><span class="chroma-p">,</span><span class="chroma-w"> </span><span class="chroma-mi">0</span><span class="chroma-p">)</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">				</span><span class="chroma-p">).</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-p">},</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-k">async</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-k">loop</span><span class="chroma-w"> </span><span class="chroma-p">{</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">				</span><span class="chroma-n">network_request</span><span class="chroma-p">().</span><span class="chroma-k">await</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">				</span><span class="chroma-n">ui</span><span class="chroma-p">.</span><span class="chroma-n">borrow_mut</span><span class="chroma-p">().</span><span class="chroma-n">value</span><span class="chroma-w"> </span><span class="chroma-o">+=</span><span class="chroma-w"> </span><span class="chroma-mi">1</span><span class="chroma-p">;</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">			</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">		</span><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-w">	</span><span class="chroma-p">);</span><span class="chroma-w">
</span></span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span><span class="chroma-w">
</span></span></span></code></pre></div>
</div>
<p>This is how I ended up organizing my TUI code <a href="https://github.com/GerardRodes/taur">which can be viewed at github</a>, it has
an abusive usage of <code>unwrap</code> but I just wanted to get it done within the weekend.
I would love to hear about how I could improve it and if I&#39;ve had some misconceptions in my mental model of how
Rust and async work.</p>
<hr/><aside class="discussion-section"><h2>Discussion</h2><ul><li><a href="https://www.reddit.com/r/rust/comments/y8puyv/singlethreaded_async_rust/"><img src="https://blog.grod.es/icons/reddit.png?v=925fe993dba7" alt=""/> Reddit</a></li></ul></aside>]]></content:encoded>
    </item>
    <item>
      <title>URL Query String as SPA Initial State</title>
      <link>https://blog.grod.es/url-query-string-as-spa-initial-state?utm_medium=feed&amp;utm_source=rss</link>
      <guid isPermaLink="true">https://blog.grod.es/url-query-string-as-spa-initial-state</guid>
      <pubDate>Thu, 14 Mar 2019 00:00:00 +0000</pubDate>
      <description>A short 2019 note about keeping SPA filter state in the URL query string so reloads and shared links show the same page state.</description>
      <dc:language>en</dc:language>
      <atom:link href="https://blog.grod.es/url-query-string-as-spa-initial-state" rel="alternate" type="text/html" hreflang="en" title="English"></atom:link>
      <atom:link href="https://blog.grod.es/url-query-string-as-spa-initial-state" rel="alternate" type="text/html" hreflang="x-default" title="Default version"></atom:link>
      <content:encoded><![CDATA[<p>Before starting, I would like to set the following quote as an axiom to be taken into account for a while</p>
<blockquote>
<p>“Any website should always display the same content for a given URL, regardless any previous state”
<br/>
― Someone with common sense</p>
</blockquote>
<p>I can think of some exceptions, like session or browser preferences, that can make differ the content between users, but It should be true for each single user experience.</p>
<p>This is something important to have in mind when building Single Page Applications.</p>
<p>A common mistake I’ve encountered many times during development is when a page displaying a results view, offers filters to adjust the search. Once the users starts tweaking with those filters the results update but the URL keeps the same inducing a URL-State desynchronization, so if the user tries to reload the page or shares the URL, the web page will not display the filtered results.
This is because the developer has directly updated the app state instead of use a router to keep the URL in sync with it.</p>
<p>Next I’m going to show some examples written using Vue and Vue Router</p>
<p>Warning: all this gists has been written directly from medium, I cannot assure they run without errors but I think that they capture the idea</p>
<h3 id="bad-implementation"><a class="heading-anchor" href="#bad-implementation">Bad implementation</a></h3>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;</span><span class="chroma-nt">template</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">&lt;</span><span class="chroma-nt">div</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">ul</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">&lt;</span><span class="chroma-nt">li</span> <span class="chroma-nt">v-for</span><span class="chroma-o">=</span><span class="chroma-s">&#34;item in results&#34; :key=&#34;item.id&#34;</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-p">{{</span> <span class="chroma-na">item.name</span>  <span class="chroma-p">}}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">&lt;/</span><span class="chroma-nt">li</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;/</span><span class="chroma-nt">ul</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">Checkbox</span> <span class="chroma-nt">v-model</span><span class="chroma-o">=</span><span class="chroma-s">&#34;filters.isNew&#34;</span><span class="chroma-p"> /&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">Pagination</span> <span class="chroma-nt">v-model</span><span class="chroma-o">=</span><span class="chroma-s">&#34;page&#34;</span><span class="chroma-p"> /&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">&lt;/</span><span class="chroma-nt">div</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;/</span><span class="chroma-nt">template</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;</span><span class="chroma-nt">script</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-kr">export</span> <span class="chroma-k">default</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">name</span><span class="chroma-o">:</span> <span class="chroma-s1">&#39;ResultsPage&#39;</span><span class="chroma-p">,</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">data</span><span class="chroma-o">:</span> <span class="chroma-p">()</span> <span class="chroma-p">=&gt;</span> <span class="chroma-p">({</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">results</span><span class="chroma-o">:</span> <span class="chroma-p">[],</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">page</span><span class="chroma-o">:</span> <span class="chroma-mi">1</span><span class="chroma-p">,</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">filters</span><span class="chroma-o">:</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nx">isNew</span><span class="chroma-o">:</span> <span class="chroma-kc">false</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">}),</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">watch</span><span class="chroma-o">:</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">page</span> <span class="chroma-p">(</span><span class="chroma-nx">page</span><span class="chroma-p">)</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">fetchResults</span><span class="chroma-p">({</span> <span class="chroma-nx">page</span> <span class="chroma-p">})</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">filters</span><span class="chroma-o">:</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nx">deep</span><span class="chroma-o">:</span> <span class="chroma-kc">true</span><span class="chroma-p">,</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nx">handler</span> <span class="chroma-p">(</span><span class="chroma-nx">filters</span><span class="chroma-p">)</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">fetchResults</span><span class="chroma-p">({</span> <span class="chroma-nx">filters</span> <span class="chroma-p">})</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">created</span> <span class="chroma-p">()</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">fetchResults</span><span class="chroma-p">()</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">methods</span><span class="chroma-o">:</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">async</span> <span class="chroma-nx">fetchResults</span> <span class="chroma-p">({</span> <span class="chroma-nx">filters</span> <span class="chroma-o">=</span> <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">filters</span><span class="chroma-p">,</span> <span class="chroma-nx">page</span> <span class="chroma-o">=</span> <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">page</span> <span class="chroma-p">})</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-kr">const</span> <span class="chroma-p">{</span> <span class="chroma-nx">data</span><span class="chroma-o">:</span> <span class="chroma-nx">results</span> <span class="chroma-p">}</span> <span class="chroma-o">=</span> <span class="chroma-nx">await</span> <span class="chroma-nx">SomeApiCall</span><span class="chroma-p">({</span> <span class="chroma-nx">filters</span> <span class="chroma-p">,</span> <span class="chroma-nx">page</span> <span class="chroma-p">})</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">results</span> <span class="chroma-o">=</span> <span class="chroma-nx">results</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;/</span><span class="chroma-nt">script</span><span class="chroma-p">&gt;</span>
</span></span></code></pre></div>
</div>
<p>Here we can see how the inputs Checkbox and Pagination are updating the data properties directly and then the watchers of them trigger the fetch of new results without updating the URL.</p>
<p>It’s wrong.</p>
<h3 id="good-implementation"><a class="heading-anchor" href="#good-implementation">Good implementation</a></h3>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;</span><span class="chroma-nt">template</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">&lt;</span><span class="chroma-nt">div</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">ul</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">&lt;</span><span class="chroma-nt">li</span> <span class="chroma-nt">v-for</span><span class="chroma-o">=</span><span class="chroma-s">&#34;item in results&#34; :key=&#34;item.id&#34;</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-p">{{</span> <span class="chroma-na">item.name</span>  <span class="chroma-p">}}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">&lt;/</span><span class="chroma-nt">li</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;/</span><span class="chroma-nt">ul</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">Checkbox</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nt">:value</span><span class="chroma-o">=</span><span class="chroma-s">&#34;filters.isNew&#34;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nt">@input</span><span class="chroma-s">=&#34;$router.push({ query: { ...$route.query, isNew: String($event) }})&#34;</span><span class="chroma-p">
</span></span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">/&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">Pagination</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nt">:value</span><span class="chroma-o">=</span><span class="chroma-s">&#34;page&#34;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nt">@input</span><span class="chroma-s">=&#34;$router.push({ query: { ...$route.query, page: $event }})&#34;</span><span class="chroma-p">
</span></span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">/&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">&lt;/</span><span class="chroma-nt">div</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;/</span><span class="chroma-nt">template</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;</span><span class="chroma-nt">script</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-kr">export</span> <span class="chroma-k">default</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">name</span><span class="chroma-o">:</span> <span class="chroma-s1">&#39;ResultsPage&#39;</span><span class="chroma-p">,</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">beforeRouteEnter</span> <span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">,</span> <span class="chroma-nx">from</span><span class="chroma-p">,</span> <span class="chroma-nx">next</span><span class="chroma-p">)</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">next</span><span class="chroma-p">(</span><span class="chroma-nx">vm</span> <span class="chroma-p">=&gt;</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nx">vm</span><span class="chroma-p">.</span><span class="chroma-nx">page</span> <span class="chroma-o">=</span> <span class="chroma-nb">Number</span><span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">.</span><span class="chroma-nx">query</span><span class="chroma-p">.</span><span class="chroma-nx">page</span> <span class="chroma-o">||</span> <span class="chroma-mi">1</span><span class="chroma-p">)</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nx">vm</span><span class="chroma-p">.</span><span class="chroma-nx">filters</span><span class="chroma-p">.</span><span class="chroma-nx">isNew</span> <span class="chroma-o">=</span> <span class="chroma-nx">to</span><span class="chroma-p">.</span><span class="chroma-nx">query</span><span class="chroma-p">.</span><span class="chroma-nx">isNew</span> <span class="chroma-o">===</span> <span class="chroma-s1">&#39;true&#39;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nx">vm</span><span class="chroma-p">.</span><span class="chroma-nx">fetchResults</span><span class="chroma-p">()</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">})</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">async</span> <span class="chroma-nx">beforeRouteUpdate</span>  <span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">,</span> <span class="chroma-nx">from</span><span class="chroma-p">,</span> <span class="chroma-nx">next</span><span class="chroma-p">)</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">page</span> <span class="chroma-o">=</span> <span class="chroma-nb">Number</span><span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">.</span><span class="chroma-nx">query</span><span class="chroma-p">.</span><span class="chroma-nx">page</span> <span class="chroma-o">||</span> <span class="chroma-mi">1</span><span class="chroma-p">)</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">filters</span><span class="chroma-p">.</span><span class="chroma-nx">isNew</span> <span class="chroma-o">=</span> <span class="chroma-nx">to</span><span class="chroma-p">.</span><span class="chroma-nx">query</span><span class="chroma-p">.</span><span class="chroma-nx">isNew</span> <span class="chroma-o">===</span> <span class="chroma-s1">&#39;true&#39;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">await</span> <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">fetchResults</span><span class="chroma-p">()</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">next</span><span class="chroma-p">()</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">data</span><span class="chroma-o">:</span> <span class="chroma-p">()</span> <span class="chroma-p">=&gt;</span> <span class="chroma-p">({</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">results</span><span class="chroma-o">:</span> <span class="chroma-p">[],</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">page</span><span class="chroma-o">:</span> <span class="chroma-mi">1</span><span class="chroma-p">,</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">filters</span><span class="chroma-o">:</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nx">isNew</span><span class="chroma-o">:</span> <span class="chroma-kc">false</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">}),</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">methods</span><span class="chroma-o">:</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">async</span> <span class="chroma-nx">fetchResults</span> <span class="chroma-p">()</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-kr">const</span> <span class="chroma-p">{</span> <span class="chroma-nx">data</span> <span class="chroma-p">}</span> <span class="chroma-o">=</span> <span class="chroma-nx">await</span> <span class="chroma-nx">SomeApiCall</span><span class="chroma-p">({</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-nx">filters</span><span class="chroma-o">:</span> <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">filters</span><span class="chroma-p">,</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-nx">page</span><span class="chroma-o">:</span> <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">page</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">})</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">results</span> <span class="chroma-o">=</span> <span class="chroma-nx">data</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;/</span><span class="chroma-nt">script</span><span class="chroma-p">&gt;</span>
</span></span></code></pre></div>
</div>
<p>On this example we use the navigation guards provided by Vue Router to hook into the cycle of the router so we can get the query params and sync our data with them</p>
<p>Even we could go further with it and get rid of the data properties to only use the params provided by the route so we don’t need to sync anything, like is shown in this last gist</p>
<div class="code-block">
<div class="code-block__body"><pre class="chroma-chroma chroma-light"><code><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;</span><span class="chroma-nt">template</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">&lt;</span><span class="chroma-nt">div</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">ul</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">&lt;</span><span class="chroma-nt">li</span> <span class="chroma-nt">v-for</span><span class="chroma-o">=</span><span class="chroma-s">&#34;item in results&#34; :key=&#34;item.id&#34;</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-p">{{</span> <span class="chroma-na">item.name</span>  <span class="chroma-p">}}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">&lt;/</span><span class="chroma-nt">li</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;/</span><span class="chroma-nt">ul</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">Checkbox</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-o">:</span><span class="chroma-na">value</span><span class="chroma-o">=</span><span class="chroma-s">&#34;$route.query.isNew === &#39;true&#39;&#34;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nt">@input</span><span class="chroma-s">=&#34;$router.push({ query: { ...$route.query, isNew: String($event) } })&#34;</span><span class="chroma-p">
</span></span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">/&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">&lt;</span><span class="chroma-nt">Pagination</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-o">:</span><span class="chroma-na">value</span><span class="chroma-o">=</span><span class="chroma-s">&#34;Number($route.query.page || 1)&#34;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-nt">@input</span><span class="chroma-s">=&#34;$router.push({ query: { ...$route.query, page: $event } })&#34;</span><span class="chroma-p">
</span></span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">/&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">&lt;/</span><span class="chroma-nt">div</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;/</span><span class="chroma-nt">template</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl">
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;</span><span class="chroma-nt">script</span><span class="chroma-p">&gt;</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-kr">export</span> <span class="chroma-k">default</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">name</span><span class="chroma-o">:</span> <span class="chroma-s1">&#39;ResultsPage&#39;</span><span class="chroma-p">,</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">beforeRouteEnter</span> <span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">,</span> <span class="chroma-nx">from</span><span class="chroma-p">,</span> <span class="chroma-nx">next</span><span class="chroma-p">)</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">next</span><span class="chroma-p">(</span><span class="chroma-nx">vm</span> <span class="chroma-p">=&gt;</span> <span class="chroma-p">{</span> <span class="chroma-nx">vm</span><span class="chroma-p">.</span><span class="chroma-nx">fetchResultsFromRoute</span><span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">)</span> <span class="chroma-p">})</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">async</span> <span class="chroma-nx">beforeRouteUpdate</span>  <span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">,</span> <span class="chroma-nx">from</span><span class="chroma-p">,</span> <span class="chroma-nx">next</span><span class="chroma-p">)</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">await</span> <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">fetchResultsFromRoute</span><span class="chroma-p">(</span><span class="chroma-nx">to</span><span class="chroma-p">)</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">next</span><span class="chroma-p">()</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">data</span><span class="chroma-o">:</span> <span class="chroma-p">()</span> <span class="chroma-p">=&gt;</span> <span class="chroma-p">({</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">results</span><span class="chroma-o">:</span> <span class="chroma-p">[]</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">}),</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-nx">methods</span><span class="chroma-o">:</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-nx">async</span> <span class="chroma-nx">fetchResultsFromRoute</span> <span class="chroma-p">({</span> <span class="chroma-nx">query</span><span class="chroma-o">:</span> <span class="chroma-p">{</span> <span class="chroma-nx">isNew</span> <span class="chroma-o">=</span> <span class="chroma-kc">false</span><span class="chroma-p">,</span> <span class="chroma-nx">page</span> <span class="chroma-o">=</span> <span class="chroma-mi">1</span> <span class="chroma-p">}</span> <span class="chroma-o">=</span> <span class="chroma-p">{})</span> <span class="chroma-p">{</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-kr">const</span> <span class="chroma-p">{</span> <span class="chroma-nx">data</span> <span class="chroma-p">}</span> <span class="chroma-o">=</span> <span class="chroma-nx">await</span> <span class="chroma-nx">SomeApiCall</span><span class="chroma-p">({</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-nx">filters</span><span class="chroma-o">:</span> <span class="chroma-p">{</span> <span class="chroma-nx">isNew</span><span class="chroma-o">:</span> <span class="chroma-nx">isNew</span> <span class="chroma-o">===</span> <span class="chroma-s1">&#39;true&#39;</span> <span class="chroma-p">},</span>
</span></span><span class="chroma-line"><span class="chroma-cl">        <span class="chroma-nx">page</span><span class="chroma-o">:</span> <span class="chroma-nb">Number</span><span class="chroma-p">(</span><span class="chroma-nx">page</span><span class="chroma-p">)</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-p">})</span>
</span></span><span class="chroma-line"><span class="chroma-cl">      <span class="chroma-k">this</span><span class="chroma-p">.</span><span class="chroma-nx">results</span> <span class="chroma-o">=</span> <span class="chroma-nx">data</span>
</span></span><span class="chroma-line"><span class="chroma-cl">    <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl">  <span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">}</span>
</span></span><span class="chroma-line"><span class="chroma-cl"><span class="chroma-p">&lt;/</span><span class="chroma-nt">script</span><span class="chroma-p">&gt;</span>
</span></span></code></pre></div>
</div>
<p>This way the browser will keep track of any changes on the state of the page and the user will have full functionality on Back/Forward actions and a fully shareable URL</p>
]]></content:encoded>
    </item>
  </channel>
</rss>