1<!DOCTYPE HTML> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>How itch.io uses Coroutines for non-blocking IO</title> 6 <link rel="stylesheet" href="../style/style.css?Fri Jan 16 20:28:13 2026" /> 7 <link rel="alternate" type="application/rss+xml" title="leafo.net posts" href="../feed.xml" /> 8 <meta name="viewport" content="width=device-width, initial-scale=1" /> 9 10 <meta property="og:site_name" content="leafo.net" /> 11 <meta property="og:title" content="How itch.io uses Coroutines for non-blocking IO" /> 12 <meta property="og:image" content="http://leafo.net/img/leafo.png" /> 13 14 <meta property="og:description" content="itch.io is a website for hosting indie games, its implementation is unique because it’s written entirely in Lua ( MoonScript ). It runs inside of an nginx distribution called OpenResty . It uses coroutines for all asynchronous operations like d..." /> 15 <meta name="description" content="itch.io is a website for hosting indie games, its implementation is unique because it’s written entirely in Lua ( MoonScript ). It runs inside of an nginx distribution called OpenResty . It uses coroutines for all asynchronous operations like d..." /> 16 17
17<script type="text/javascript"> 18 window.site_root = ".."; 19 </script>
19 20
20<script type="text/javascript"> 21 if (window.location.hostname != "localhost") { 22 var _gaq = _gaq || []; 23 _gaq.push(['_setAccount', 'UA-136625-1']); 24 _gaq.push(['_trackPageview']); 25 26 (function() { 27 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 28 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 29 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 30 })(); 31 } 32</script>
32 33 <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,700" rel="stylesheet"> 34</head> 35<body class="nice_fonts"> 36 <div class="post"><div class="sidebar"><div class="logo_wrapper"><a title="leafo.net" href=".."><img alt="leafo.net" class="logo" src="../img/leafo.svg"/></a></div><div class="sidebar_content"><div class="sidebar_minor"><h3>My projects</h3><ul><li><a href="https://sightreading.training">sightreading.training</a></li><li><a href="https://streak.club">streak.club</a></li><li><a href="https://leafo.net/lapis/">Lapis</a></li><li><a href="https://itch.io">itch.io</a></li><li><a href="https://moonscript.org">MoonScript</a></li><li><a href="https://ludumdare.itch.io">Ludum Dare game browser</a></li></ul><p><a href="..#projects">View more â</a></p><h3>Recent guides</h3><ul><li><a href="../guides/testing-lua-modules-with-github-actions.html">Testing Lua projects with GitHub Actions</a></li><li><a href="../guides/postgresql-calculating-percentile.html">Calculating Percentile (and Median) in PostgreSQL</a></li><li><a href="../guides/nginx-reverse-proxy-to-self.html">NGINX reverse proxy to self</a></li><li><a href="../guides/postgresql-preloading.html">PostgreSQL Preloading</a></li><li><a href="../guides/building-openresty-with-openssl-1-0.html">Building OpenResty with OpenSSL 1.0</a></li><li><a href="../guides/customizing-the-luarocks-tree.html">Using LuaRocks to install packages in the current directory</a></li><li><a href="../guides/dynamic-scoping-in-lua.html">Dynamic scoping in Lua</a></li><li><a href="../guides/dsl-in-lua.html">Writing a DSL in Lua</a></li><li><a href="../guides/function-cloning-in-lua.html">Cloning a function in Lua</a></li><li><a href="../guides/setfenv-in-lua52-and-above.html">Implementing setfenv in Lua 5.2, 5.3, and above</a></li></ul><p><a href="../guides.html">View all â</a></p><h3>Recent posts</h3><ul><li><a href="../posts/a-new-leafonet.html">A new leafo.net</a></li><li><a href="../posts/introducing_streakclub.html">Introducing Streak Club</a></li><li><a href="../posts/creating_an_image_server.html">Nginx image processing server</a></li><li><a href="../posts/moonscript_v024.html">MoonScript v0.2.4</a></li><li><a href="../posts/introducing_itchio.html">Introducing itch.io</a></li><li><a href="../posts/moonscript_v023.html">MoonScript v0.2.3</a></li><li><a href="../posts/moonscript_v022.html">MoonScript v0.2.2</a></li><li><a href="../posts/getting_started_with_moonscript.html">Getting started with MoonScript</a></li><li><a href="../posts/lua_on_heroku.html">Running Lua on Heroku</a></li><li><a href="../posts/moonscript_v020.html">MoonScript v0.2.0</a></li></ul></div></div></div><div class="main_column"><div class="post_header"><h1>How itch.io uses Coroutines for non-blocking IO</h1><div class="post_meta">Posted <span class="post_date" title="Thu Jun 09 2016 09:24:37">June 09, 2016</span> by leafo (<a href="https://twitter.com/moonscript">@moonscript</a>) · Tags: lua, moonscript</div><div class="discussions">
36<script type="text/javascript">reddit_url = "http:\/\/leafo.net\/posts\/itchio-and-coroutines.html"</script>
36<script type="text/javascript" src="//www.redditstatic.com/button/button1.js"></script>
36<a class="twitter-share-button" href="https://twitter.com/share" data-url="http://leafo.net/posts/itchio-and-coroutines.html" data-via="moonscript" data-hashtags="lualang">Tweet</a>
36<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
36 37 </div></div><div class="inner post_body"><p><a href="https://itch.io">itch.io</a> is a website for hosting indie games, its implementation is 38unique because it’s written entirely in Lua (<a href="http://moonscript.org">MoonScript</a>). It runs inside 39of an nginx distribution called <a href="https://openresty.org/en/">OpenResty</a>. It uses coroutines for all 40asynchronous operations like database queries and HTTP requests.</p> 41 42<div class="index"><ul><li><a href="#overview-of-coroutines">Overview of coroutines</a></li> 43<li><a href="#coroutines-in-practice">Coroutines in practice</a></li> 44<ul><li><a href="#coroutines-in-practice/whats-wrong-with-async-callbacks">What’s wrong with async callbacks?</a></li> 45<li><a href="#coroutines-in-practice/the-coroutine-approach">The coroutine approach </a></li> 46<li><a href="#coroutines-in-practice/how-openresty-works">How OpenResty works</a></li></ul> 47<li><a href="#conclusion">Conclusion</a></li></ul></div> 48 49 50<p>I like Lua for many reasons<sup>1</sup>, it gets the design of many core 51components correct:</p> 52 53<ul> 54<li>Errors are handled locally for functional calls — we use multiple return values to signal status without additional allocations. (no temporary arrays, or exceptions)</li> 55<li>Prototypical inheritance is implemented in a smart way — there are no <em>reserved</em> hash table keys to change functionality. (looking at you <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol">JavaScript Symbol</a>)</li> 56<li><strong>Coroutines are implemented as first-class functionality</strong>, and not tacked on as additional syntax like many other languages.</li> 57</ul> 58 59 60<h2 id="overview-of-coroutines">Overview of coroutines</h2> 61 62<p>Coroutines are a tool, but they probably aren’t a tool you're familiar with. 63When you first started programming you may have viewed regular expressions as 64some mystical thing that you didn’t really get, but let you do some powerful 65things. As you matured as a programmer you learned the syntax, and learned when 66and how to use them effectively. Coroutines are in the same category. The only 67problem is that most languages don’t expose coroutines like they do regular 68expressions, so you haven’t had the opportunity to use them effectively.</p> 69 70<p>This post is about coroutines. <strong>When coroutines are done right, they can be 71used as an implementation detail and not a syntax change.</strong> Examples of 72languages that do it right include Lisp and Lua. (I'm sure there are more, but 73I haven’t used them.)</p> 74 75<p>The best way to think of coroutines is to understand where they sit in the 76taxonomy of <em>control transfer</em>. Chances are you're already familiar with 77exceptions. I like to sort things like this:</p> 78 79<pre><code>Exceptions < Coroutines < Continuations 80</code></pre> 81 82<p><strong>Exceptions</strong> can be thought of as a <em>subclass</em> of coroutines. You can 83implement an exception mechanism with coroutines. They are an abstract 84construct that let you control where your code is executing (see 85<a href="http://man7.org/linux/man-pages/man3/longjmp.3.html"><code>longjmp</code></a>). When there’s an error your <a href="https://en.wikipedia.org/wiki/Program_counter">program counter</a> jumps to the 86<code>try</code> <code>catch</code> block you've written further up in the execution stack. Jumps 87like this are called control transfer.</p> 88 89<p>When an exception happens you lose access to where the error happened. 90(Although exception implementations will capture information about where the 91error happened, like a backtrace, and return it in an exception object.) You 92can’t say <em>try running that line again</em>, which makes sense, because that line 93resulted in an error.</p> 94 95<p>With coroutines you can tell the code to jump back and start running exactly 96where it left off. We don’t use coroutines for unrecoverable errors, there are 97many scenarios where it makes sense to go back to where we were and keep on 98executing. This is called <a href="https://en.wikipedia.org/wiki/Thread_%28computing%29#Scheduling">cooperative multithreading</a>.</p> 99 100<p>Coroutines do not use <code>try</code> <code>catch</code> or <code>throw</code>. In Lua, after creating a 101coroutine, you run it, just like a function, and it returns values, just like a 102function. Instead of using <code>return</code>, a coroutine can <em>yield</em> to signal that it 103might want to return back to that spot. When a yield happens, control is 104transferred back to where the coroutine was initially executed. The original 105caller can decide to <em>resume</em> the coroutine if it makes sense.</p> 106 107<p><strong>Continuations</strong> are like coroutines but with <em>even more control</em>. You can 108return to the same location multiple times. Additionally, when continuations 109are implemented as first-class objects, you can pass around a control transfer 110object. You might be starting to see the parallels to how first-class functions 111work.</p> 112 113<h2 id="coroutines-in-practice">Coroutines in practice</h2> 114 115<h3 id="coroutines-in-practice/whats-wrong-with-async-callbacks">What’s wrong with async callbacks?</h3> 116 117<p>I mentioned above that coroutines work best when they're <strong>an implementation 118detail and not a syntax change</strong>. As the consumer of some 119interface/library/framework, you should be able to take advantage of 120coroutines, but it shouldn’t require you to change how you write code.</p> 121 122<p>JavaScript definitely popularized asynchronous code via callbacks. It also 123completely changed how many of us write code. For complicated applications, it 124changed it for the worse.</p> 125 126<p>Now that Node.js has brought server side JavaScript to the masses, a common 127task for a web developer is to run a few queries that are dependent on one 128another, then return the result. Queries should be executed asynchronous since 129they preform networks operations that should not block the main thread.</p> 130 131<p>The naïve approach looks something like this:</p> 132 133<pre class="highlight lang_javascript"><code><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">getUserProfileLinks</span><span class="p">(</span><span class="nx">userId</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> 134<span class="w"> </span><span class="nx">query</span><span class="p">(</span><span class="s2">"select * from users where id = ? limit 1"</span><span class="p">,</span><span class="w"> </span><span class="nx">userId</span><span class="p">,</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">users</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> 135<span class="w"> </span><span class="nx">query</span><span class="p">(</span><span class="s2">"select * from profiles where user_id = ? limit 1"</span><span class="p">,</span><span class="w"> </span><span class="nx">users</span><span class="p">[</span><span class="mf">0</span><span class="p">].</span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">profiles</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> 136<span class="w"> </span><span class="nx">query</span><span class="p">(</span><span class="s2">"select * from profile_links where profile_id = ?"</span><span class="p">,</span><span class="w"> </span><span class="nx">profile</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">links</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> 137<span class="w"> </span><span class="c1">// return links??</span> 138<span class="w"> </span><span class="p">})</span> 139<span class="w"> </span><span class="p">})</span> 140<span class="w"> </span><span class="p">})</span> 141 142<span class="w"> </span><span class="c1">// ?? can't return here since everything is async, see above</span> 143<span class="p">}</span></code> 144</pre> 145 146 147<p>As more queries are added the nesting becomes extreme. Additionally, the real 148disadvantage, it changes the interface to our function. It’s impossible to 149return the result because we don’t have the final query’s response. You must 150restructure your functions to no longer use return values, but instead take a 151callback.</p> 152 153<p>The code above doesn’t even handle errors. Do we add a second callback for 154errors? Do we pass a special argument that represents an error to the one 155callback? Assuming we decide on how we want to handle errors, how do 156we get everyone else to agree with it so we can have uniform syntax?</p> 157 158<p>This is an actual problem in the JavaScript community: There are still people 159building new libraries trying to figure out the best way to manage asynchronous 160control. A language that creates this kind of schism in the community is not a 161well designed language.</p> 162 163<p>It gets worse…</p> 164 165<p>This asynchronous interface contaminates your code. If you have any other 166function in your application that needs to work with this function, it then 167needs to be rewritten asynchronous-callback style to match the control 168transfer, <em>even if this hypothetical function does nothing asynchronous</em>. This 169is a massive overhead for developers.</p> 170 171<p>If someone writes a library that abstracts all this database logic away, you, 172as the consumer, must use their asynchronous interface. The asynchronous
173interface will permeate throughout your code base, even if you never write 174anything asynchronous. You can not hide asynchronous control transfer via 175callbacks within a procedural interface.</p> 176 177<p>Luckily the JavaScript community is coming to agreements on how to handle 178asynchronous interfaces. But the core problems still stands: <strong>We have two 179competing ways to do flow control in our code base.</strong></p> 180 181<h3 id="coroutines-in-practice/the-coroutine-approach">The coroutine approach </h3> 182 183<p>Here’s an how I would write the snippet above for itch.io with Lua running 184within OpenResty:</p> 185 186<pre class="highlight lang_lua"><code><span></span><span class="kr">function</span><span class="w"> </span><span class="nf">get_links</span><span class="p">(</span><span class="nv">user_id</span><span class="p">)</span> 187<span class="w"> </span><span class="kd">local</span><span class="w"> </span><span class="nv">users</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">query</span><span class="p">(</span><span class="s2">"select * from users where id = ? limit 1"</span><span class="p">,</span><span class="w"> </span><span class="nv">user_id</span><span class="p">)</span> 188<span class="w"> </span><span class="kd">local</span><span class="w"> </span><span class="nv">profiles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">query</span><span class="p">(</span><span class="s2">"select * from profiles where user_id = ? limit 1"</span><span class="p">,</span><span class="w"> </span><span class="nv">users</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> 189<span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="nf">query</span><span class="p">(</span><span class="s2">"select * from profile_links where profile_id = ?"</span><span class="p">,</span><span class="w"> </span><span class="nv">profiles</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> 190<span class="kr">end</span></code> 191</pre> 192 193 194<p><strong>Whoa</strong>, wait a second: that’s just procedural code. That’s exactly why 195coroutines are important. Coroutines do not require us to rewrite our code. 196OpenResty gives an environment to execute in where the implementation of my 197non-blocking functions, like <code>query</code>, can yield when it’s time to give control 198back to the scheduler.</p> 199 200<p><strong>As an application developer I change <em>nothing</em> about how I write code.</strong> 201Coroutines are an implementation detail of the <code>query</code> function, not a 202requirement on how I must structure my business logic.</p> 203 204<p>What about errors? Easy: I use the same interface as any other function. In Lua 205errors are handled by returning a second value. And since I'm just writing 206procedural code, I can <code>return</code> on error:</p> 207 208<pre class="highlight lang_lua"><code><span></span><span class="kr">function</span><span class="w"> </span><span class="nf">get_links</span><span class="p">(</span><span class="nv">user_id</span><span class="p">)</span> 209<span class="w"> </span><span class="kd">local</span><span class="w"> </span><span class="nv">users</span><span class="p">,</span><span class="w"> </span><span class="nv">err</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">query</span><span class="p">(</span><span class="s2">"select * from users where id = ? limit 1"</span><span class="p">,</span><span class="w"> </span><span class="nv">user_id</span><span class="p">)</span> 210<span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="nv">users</span><span class="w"> </span><span class="kr">then</span> 211<span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nv">err</span> 212<span class="w"> </span><span class="kr">end</span> 213<span class="w"> </span><span class="c1">-- etc.</span> 214<span class="kr">end</span></code> 215</pre> 216 217 218<p>The framework developers determine how the code executes, not me. This is 219perfect, since writing the asynchronous callback code in JavaScript is just 220busy work.</p> 221 222<p>It gets even better: In my test environment I don’t need non-blocking code, I 223don’t have a scheduler, I don’t even run my code inside of OpenResty. <strong>It 224doesn’t matter.</strong> I can provide my own implementation of <code>query</code> which operates 225synchronously. My business logic runs exactly the same. If I'm upgrading from 226an old synchronous project to non-blocking IO, I don’t have to rewrite 227everything.</p> 228 229<p><strong>Runtime errors are what you expect:</strong> Since each coroutine has its own stack, 230when a runtime exception happens (eg. I try to dereference <code>nil</code>), my backtrace 231only contains the stack that my coroutine is running in. Error backtraces are 232short and easy to parse.</p> 233 234<p>Since there is only one function interface, non-blocking code already works 235fine with all existing code. There are no new interfaces to learn, and there is 236no confusion about the best way to call a function. The language gives us an
237interface we can all agree on.</p> 238 239<h3 id="coroutines-in-practice/how-openresty-works">How OpenResty works</h3> 240 241<p>OpenResty is clever about their implementation for non-blocking IO. OpenResty 242re-implements the <code>socket</code> interface provided by <code>LuaSocket</code>. It directs all 243network activity to non-blocking IO managed by Nginx’s event loop. Every time 244Lua code triggers a network operation, control is yielded back to Nginx where 245it can resume any coroutines that have IO ready for reading. <strong>Although I'm 246using cooperative multithreading, I don’t have to think about yielding</strong>.</p> 247 248<p>Why is this important? <strong>I can use the same exact library</strong>, and have it run 249either synchronous on the command line or non-blocking inside of OpenResty. Not 250only have coroutines saved my sanity as an application developer, but also as a 251library developer. I've done exactly this with my Lua PostgreSQL driver: 252<a href="https://github.com/leafo/pgmoon">pgmoon</a>.</p> 253 254<h2 id="conclusion">Conclusion</h2> 255 256<p>I hope this post encourages you to check out <a href="https://openresty.org/en/">OpenResty</a>. If you're looking 257for a full featured web framework to use with it then I suggest <a href="http://leafo.net/lapis/">Lapis</a>. 258itch.io has been running Lapis in production for over 3 years. I've written
259over 100 thousand lines of MoonScript for itch.io without a single asynchronous 260callback.</p> 261 262<p>The slowest asynchronous APIs that itch.io has to communicate with are payment 263gateways. I remember speaking to another marketplace about how they needed to 264build new queueing infrastructure to handle all the asynchronous communication 265with their payment gateways. Since they were using a blocking web framework, 266Rails, their throughput would be destroyed had they tried wait for PayPal to 267respond in their web app.</p> 268 269<p>In itch.io I just write <code>http.request()</code> and read the return value. Throughput 270is unaffected. Imagine saving hundreds of thousands of dollars and months of 271headaches because you decided to learn about coroutines. ð</p> 272 273<hr /> 274 275<p><sup>1</sup> These are just <em>some</em> of my favorites, check out the <a href="https://www.lua.org/manual/5.3/">Lua reference manual for more</a>.</p> 276<div class="tagged_pages"><div class="more_posts_header">Here are some more guides tagged 'lua'</div><div class="related_page_box"><div class="page_title"><a href="../guides/testing-lua-modules-with-github-actions.html">Testing Lua projects with GitHub Actions</a></div><div class="sub">Posted <span title="Sun 26 Apr 2020 11:06:30 AM PDT">April 26, 2020</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/customizing-the-luarocks-tree.html">Using LuaRocks to install packages in the current directory</a></div><div class="sub">Posted <span title="Thu Jan 28 01:45:11 PST 2016">January 28, 2016</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/dynamic-scoping-in-lua.html">Dynamic scoping in Lua</a></div><div class="sub">Posted <span title="Sun Jan 24 00:53:45 PST 2016">January 24, 2016</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/dsl-in-lua.html">Writing a DSL in Lua</a></div><div class="sub">Posted <span title="Sat Aug 8 12:20:12 PDT 2015">August 08, 2015</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/function-cloning-in-lua.html">Cloning a function in Lua</a></div><div class="sub">Posted <span title="Wed Jul 8 13:04:58 PDT 2015">July 08, 2015</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/setfenv-in-lua52-and-above.html">Implementing setfenv in Lua 5.2, 5.3, and above</a></div><div class="sub">Posted <span title="Tue Jul 7 19:46:23 2015 -0700">July 08, 2015</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/moonscript-classes.html">An in-depth look into the MoonScript class implementation</a></div><div class="sub">Posted <span title="Sun Jul 5 01:19:19 2015 -0700">July 05, 2015</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/parsing-expression-grammars.html">An introduction to Parsing Expression Grammars with LPeg</a></div><div class="sub">Posted <span title="Sat Jul 4 00:35:09 PDT 2015">July 04, 2015</span></div></div><div class="related_page_box"><div class="page_title"><a href="../guides/using-postgres-with-openresty.html">Using PostgreSQL with OpenResty</a></div><div class="sub">Posted <span title="Fri Jul 3 21:28:18 PDT 2015">July 04, 2015</span></div></div></div></div></div><div class="misc_column"><h3>Related projects</h3><div class="project_box"><div class="project_name"><a href="https://moonscript.org">MoonScript</a></div><p class="project_blurb">A programming language that compiles to Lua, inspired by CoffeeScript and written in MoonScript.</p><div class="project_buttons"><a href="https://github.com/leafo/moonscript">GitHub Repo</a></div></div><div class="project_box"><div class="project_name"><a href="https://github.com/leafo/pgmoon">pgmoon</a></div><p class="project_blurb">A PostgreSQL client library written in pure Lua, designed for standalone use and use within OpenResty</p><div class="project_buttons"><a href="https://github.com/leafo/pgmoon">GitHub Repo</a></div></div></div></div> 277 278 <div class="footer"> 279 <p> 280 leafo.net · Generated Fri Jan 16 20:28:13 2026 by <a href="../sitegen">Sitegen</a> 281 <a rel="me" href="https://mastodon.social/@leafo">mastodon.social/@leafo</a> 282 </p> 283 </div> 284 285
285<script type="text/javascript"> 286 if (navigator.appVersion.indexOf("Win") >= 0) { 287 document.body.className = ""; 288 } 289 </script>
289 290</body> 291</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.