Floobits News Floobits 2016-08-18T15:20:23-07:00 https://news.floobits.com/ Floobits Atom: Fixing Bugs (in Atom) 2016-08-02T18:49:23-07:00 https://news.floobits.com/2016/08/02/floobits-atom-fixing-bugs/ <p>Our Atom package has been improved significantly since it was originally announced. Several of these improvements were thanks to fixes in Atom.</p> <h2 id="fixing-video-chat-content-security-policy">Fixing Video Chat: Content Security Policy</h2> <p>When Atom 1.7 was released, users complained that <a href="https://github.com/Floobits/floobits-atom/issues/114">video chat showed a black screen</a>. Our plugin hadn’t changed, so I suspected something in Atom was responsible. I soon tracked it down to <a href="https://en.wikipedia.org/wiki/Content_Security_Policy">Content Security Policy</a> headers. The new version of Atom used a new version of Electron, which was based on a newer version of Chromium. The newer Chromium had <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=473904">stricter CSP behavior</a>, such that <code>self</code> no longer included <code>blob:</code>. I found the relevant code in Atom and <a href="https://github.com/atom/atom/pull/11552">submitted a pull request</a>. It took a while for the Atom team to merge it, but the fix is now in Atom 1.9.</p> <h2 id="fixing-crashes-avoiding-nodeenv-erasure">Fixing Crashes: Avoiding <code>NODE_ENV</code> Erasure</h2> <p>Another issue that had been plaguing our Atom package was random React-related crashes. None of the stack traces involved any Floobits code, so it took me a while to figure out the root cause. Try to find the common thread in all of these issues:</p> <ul> <li><a href="https://github.com/Floobits/floobits-atom/issues/121">Floobits-atom: Uncaught TypeError: Cannot read property ‘validated’ of undefined #121</a></li> <li><a href="https://github.com/Floobits/floobits-atom/issues/123">Floobits-atom: Uncaught TypeError: warnUnknownProperty is not a function</a></li> <li><a href="https://github.com/Floobits/floobits-atom/issues/127">Floobits-atom: Uncaught TypeError: Cannot read property ‘null’ of undefined</a></li> <li><a href="https://github.com/Floobits/atom-term3/issues/56">Floobits-term3: Uncaught TypeError: Expecting a function in instanceof check, but got #&lt;Collection&gt;</a></li> <li><a href="https://github.com/Floobits/atom-term3/issues/63">Floobits-term3: Uncaught TypeError: Cannot read property ‘null’ of undefined</a></li> </ul> <p>I went gallivanting through the React code and managed to figure it out. React was starting up in production mode, then later switching to debug mode. React only initializes certain debugging data structures if it starts up in debug mode. Starting in production and switching to debug mode caused it to attempt to access object properties that didn’t exist. Oops.</p> <p>React chooses modes by checking <code>process.env.NODE_ENV</code>. If it’s set to “production”, it will run in production mode. Otherwise, it uses debug mode. Atom initializes <code>NODE_ENV</code> to “production” in <a href="https://github.com/atom/atom/blob/a5fdf3e18a512349e7efb91b3c297b1a2b91bf63/src/initialize-application-window.coffee#L20">src/initialize-application-window.coffee</a>. Something must change it later.</p> <p>I finally tracked it down to <a href="https://github.com/atom/atom/blob/a5fdf3e18a512349e7efb91b3c297b1a2b91bf63/src/environment-helpers.js#L75">src/environment-helpers.js</a>:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="lineno">70</span> <span class="c1">// Fix for #11302 because `process.env` on Windows is a magic object that offers case-insensitive</span> <span class="lineno">71</span> <span class="c1">// environment variable matching. By always cloning to `process.env` we prevent breaking the</span> <span class="lineno">72</span> <span class="c1">// underlying functionality.</span> <span class="lineno">73</span> <span class="kd">function</span> <span class="nx">clone</span> <span class="p">(</span><span class="nx">to</span><span class="p">,</span> <span class="nx">from</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">74</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">key</span> <span class="k">in</span> <span class="nx">to</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">75</span> <span class="hll"> <span class="k">delete</span> <span class="nx">to</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> </span><span class="lineno">76</span> <span class="p">}</span> <span class="lineno">77</span> <span class="lineno">78</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nx">to</span><span class="p">,</span> <span class="nx">from</span><span class="p">)</span> <span class="lineno">79</span> <span class="p">}</span></code></pre></figure> <p>This <code>clone()</code> function is called when running <code>atom</code> from the command line. If Atom is already running, it replaces the existing environment with the new one, which usually lacks <code>NODE_ENV</code>. Oops.</p> <p>I created <a href="https://github.com/atom/atom/issues/12024">an issue describing the problem</a>, soon followed by <a href="https://github.com/atom/atom/pull/12028">a pull request to fix it</a>.</p> <h2 id="a-side-note-nodes-processenv">A Side Note: Node’s <code>process.env</code></h2> <p>While working on the <code>NODE_ENV</code> issue, I discovered some surprising behavior in Node’s <code>process.env</code>. Look at this REPL interaction:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="o">&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TEST</span> <span class="o">=</span> <span class="kc">undefined</span> <span class="kc">undefined</span> <span class="o">&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TEST</span> <span class="s1">&#39;undefined&#39;</span> <span class="o">&gt;</span> <span class="k">typeof</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TEST</span> <span class="s1">&#39;string&#39;</span></code></pre></figure> <p>That’s right: <code>process.env</code> stringifies any value you try to set. The only way to un-set an environment variable is to <code>delete</code> it. This isn’t as crazy as it first sounds. Environment variables can only be strings. Still, such behavior is not obvious. <a href="https://nodejs.org/api/process.html#process_process_env">It’s documented</a>, but let’s be honest: Developers only read docs when they don’t think they understand something. If <code>process.env</code> looks like an object and walks like and object and quacks like an object, developers will think it’s an object. A better solution would be to have explicit <code>process.setenv()</code> and <code>process.getenv()</code> functions.</p> <h2 id="conclusion">Conclusion</h2> <p>Fixing these bugs taught me some new things about browsers, Atom, and Node.js. No matter how much experience one has with these software projects, there’s always plenty more to learn. To debug software is to be constantly reminded of one’s hubris.</p> <p>Lastly: If you’ve tried Floobits for Atom before and found it lacking, I urge you to check it out again. In addition to these bug fixes, the UI has been revamped. It all makes for a much better experience. And if you do find issues, please <a href="https://github.com/Floobits/floobits-atom/issues">report them</a>. User feedback is incredibly helpful!</p> Lodash v4.0.0: Not All Bugs are Created `isEqual()` 2016-01-25T00:58:28-08:00 https://news.floobits.com/2016/01/25/lodash-v400-not-all-bugs-are-created-equal/ <p>On January 13th (two weeks ago), <a href="https://lodash.com/">Lodash</a> v4.0 <a href="https://github.com/lodash/lodash/releases/tag/4.0.0">was released</a>. After waiting a week (for others to shake out the bugs), I branched our repos, bumped the lodash dependency in them, and fixed compatibility issues. I tried <a href="https://github.com/lodash/lodash-migrate">lodash-migrate</a>, but it wasn’t particularly useful. <code>lodash-migrate</code> works by running all lodash functions twice (once with the old version and once with the new), then logging if the results are different. That means any code with side effects will trigger a false positive or just break. Instead, I read <a href="https://github.com/lodash/lodash/wiki/Changelog#compatibility-warnings">the v4.0 changelog</a> and reviewed every incompatible call in our codebase. (This wasn’t as hard as it sounds. There were only a few dozen.) After reviewing, testing, and manually poking around after deploying to staging, I deployed the new release to prod. Success!</p> <p>…or maybe not.<!--more--> Soon after deploying, I noticed increased load on our back-end servers. When I ssh’d in to diagnose the issue, I saw that our back-end was replicating data like crazy. This didn’t endanger any information, but it was incredibly wasteful. Digging deeper, I noticed that if a single buffer in a workspace had changed, the entire contents of the workspace were copied in the next replication pass. Clearly, the lodash upgrade was responsible for this behavior, but how? I went back to my editor and looked at the code responsible:</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="lineno">187</span> <span class="p">...</span> <span class="lineno">188</span> <span class="kd">let</span> <span class="nx">local_buf</span> <span class="o">=</span> <span class="nx">local_bufs</span><span class="p">[</span><span class="nx">rbuf</span><span class="p">.</span><span class="nx">id</span><span class="p">];</span> <span class="lineno">189</span> <span class="nx">rbuf</span><span class="p">.</span><span class="nx">deleted</span> <span class="o">=</span> <span class="o">!!</span><span class="nx">rbuf</span><span class="p">.</span><span class="nx">deleted</span><span class="p">;</span> <span class="lineno">190</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">local_buf</span> <span class="o">||</span> <span class="nx">local_buf</span><span class="p">.</span><span class="nx">md5</span> <span class="o">!==</span> <span class="nx">rbuf</span><span class="p">.</span><span class="nx">md5</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">191</span> <span class="nx">log</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="s2">&quot;to_fetch: %s/%s&quot;</span><span class="p">,</span> <span class="nx">workspace_id</span><span class="p">,</span> <span class="nx">rbuf</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="lineno">192</span> <span class="nx">to_fetch</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">rbuf</span><span class="p">);</span> <span class="lineno">193</span> <span class="nx">buf_cb</span><span class="p">();</span> <span class="lineno">194</span> <span class="k">return</span><span class="p">;</span> <span class="lineno">195</span> <span class="p">}</span> <span class="lineno">196</span> <span class="k">if</span> <span class="p">(</span><span class="nx">_</span><span class="p">.</span><span class="nx">isEqual</span><span class="p">(</span><span class="nx">local_buf</span><span class="p">,</span> <span class="nx">rbuf</span><span class="p">))</span> <span class="p">{</span> <span class="lineno">197</span> <span class="nx">log</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="s2">&quot;Local copy of %s/%s matches remote. Not fetching.&quot;</span><span class="p">,</span> <span class="nx">workspace_id</span><span class="p">,</span> <span class="nx">rbuf</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="lineno">198</span> <span class="nx">buf_cb</span><span class="p">();</span> <span class="lineno">199</span> <span class="k">return</span><span class="p">;</span> <span class="lineno">200</span> <span class="p">}</span> <span class="lineno">201</span> <span class="nx">log</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&quot;Local copy of %s/%s differs from remote. Fetching.&quot;</span><span class="p">,</span> <span class="nx">workspace_id</span><span class="p">,</span> <span class="nx">rbuf</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="lineno">202</span> <span class="nx">log</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="s2">&quot;local: %s remote %s&quot;</span><span class="p">,</span> <span class="nx">_</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">local_buf</span><span class="p">),</span> <span class="nx">_</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">rbuf</span><span class="p">));</span> <span class="lineno">203</span> <span class="k">if</span> <span class="p">(</span><span class="nx">settings</span><span class="p">.</span><span class="nx">log_data</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">204</span> <span class="nx">log</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="s2">&quot;local: %s&quot;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">local_buf</span><span class="p">));</span> <span class="lineno">205</span> <span class="nx">log</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="s2">&quot;remote: %s&quot;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">rbuf</span><span class="p">));</span> <span class="lineno">206</span> <span class="p">}</span> <span class="lineno">207</span> <span class="nx">to_fetch</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">rbuf</span><span class="p">);</span> <span class="lineno">208</span> <span class="p">...</span></code></pre></figure> <p>I could reproduce the issue on staging, so I enabled debug logging and data logging. I immediately saw that not only did <code>rbuf</code> and <code>local_buf</code> have the keys, they also had the same the data! What was going on?! The only thing that changed was lodash, so I opened a REPL and double-checked the behavior of <code>_.isEqual()</code>:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">&gt; require(&quot;lodash&quot;).isEqual({&quot;a&quot;: &quot;a&quot;, &quot;b&quot;: &quot;b&quot;}, {&quot;a&quot;: &quot;a&quot;, &quot;b&quot;: &quot;b&quot;}) true &gt; require(&quot;lodash&quot;).isEqual({&quot;a&quot;: &quot;a&quot;, &quot;b&quot;: &quot;b&quot;}, {&quot;b&quot;: &quot;b&quot;, &quot;a&quot;: &quot;a&quot;}) false</code></pre></figure> <p>WTF? I couldn’t believe it. I ran similar object comparisons a dozen times. I triple-checked the JS objects I was comparing. I <em>had</em> to be making a mistake. I thought, “This can’t be a problem with lodash. It’s so basic.” I double-checked the ECMA spec to make sure object keys aren’t ordered. (They’re not.) I installed lodash 3 and re-ran the offending line in a REPL:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">&gt; require(&quot;lodash&quot;).isEqual({&quot;a&quot;: &quot;a&quot;, &quot;b&quot;: &quot;b&quot;}, {&quot;b&quot;: &quot;b&quot;, &quot;a&quot;: &quot;a&quot;}) true</code></pre></figure> <p>At this point, I was pretty sure this was a bug in lodash. I went to lodash’s GitHub project and prepared a bug report. Out of habit, I searched for existing issues. The result? <a href="https://github.com/lodash/lodash/issues/1758">An issue that had been closed 10 days earlier</a>. A fix had been committed, but a new release hadn’t been tagged. <code>:(</code>To fix the issue in production, I applied the patch to lodash in each project’s <code>node_modules</code>, then deployed again. Not ideal, but at least the bug didn’t affect Floobits anymore.</p> <p>Despite my comment on the issue, a new release <em>still</em> hasn’t been tagged. In my opinion, this is a critical bug. Object comparison is simply broken in lodash 4.0.0. I’m not sure why there’s been such a delay. While the code has been committed, a bug isn’t truly fixed until it’s deployed.</p> New Feature: Atom Chat 2015-12-20T15:49:40-08:00 https://news.floobits.com/2015/12/20/new-feature-atom-chat/ <p><a href="/2015/10/14/developing-atom-plugins-so-much-potential-so-many-bugs/">A few months ago</a>, we launched <a href="https://github.com/Floobits/floobits-atom">Floobits for Atom</a>. Since then, we’ve been fixing bugs and adding features. Ever since the initial release, users have been asking for the ability to text chat. Well, it’s here.</p> <p><img src="/images/Screen Shot 2015-12-17 at 21.27.06.png" alt="Floobits Chat in Atom" /></p> <p>Thank you, everyone, for being patient!</p> <!--more--> New Feature: Request Code Review 2015-12-02T12:22:49-08:00 https://news.floobits.com/2015/12/02/new-feature-request-code-review/ <p>If you use our web-based editor, you may have noticed a button in the upper-right with the label, “Request Code Review.”</p> <p><img src="/images/Screen Shot 2015-12-14 at 10.56.10.png" alt="Web editor request code review" /></p> <p>The button does exactly what it says. If you click on it and give us a short description of what you want reviewed, we’ll get back to you with an estimate. We can even schedule a time to video chat or pair program with you.</p> <p>Now, we’re bringing this feature to our editor plugins. If you use Atom, IntelliJ, or Sublime Text, you can request code reviews. If you’re connected to a workspace, this option is available in each editor’s command palette.</p> New Feature: Custom Highlight Colors 2015-11-09T13:55:36-08:00 https://news.floobits.com/2015/11/09/new-feature-custom-highlight-colors/ <p>If you use Floobits with IntelliJ, Atom, or our web editor, you’ve probably noticed that users’ selections have have different colors. You might also have noticed that these colors can’t be changed. If you get unlucky, your colleagues might have the same color. Then it’s hard to tell who is editing what.</p> <p>Well, good news! We’ve updated our plugins and our site to allow each users to change their highlight color. If you’re not satisfied with your default color, just go to <a href="https://floobits.com/dash/settings#user">your user settings</a> and select a new color from the drop-down. I’m a fan of lime.</p> <p>Users occasionally asked us for this feature, but we didn’t deem it high priority until one organization was unlucky enough to have <em>three</em> of their devs stuck with the same color. Oops. At least it won’t be a problem anymore. Sorry.</p> <p>Again, be sure to update your plugins! If you don’t, you’ll still see old the colors based on each username.</p> <p>Lastly, we love feedback from users. If you have any Floobits-related comments or suggestions, don’t hesitate to <a href="mailto:info@floobits.com">contact us</a>.</p> Bad Base64, a Not-so-Tricky Bug 2015-11-02T12:58:41-08:00 https://news.floobits.com/2015/11/02/bad-base64-a-not-so-tricky-bug/ <p>Last week, I explained how we found <a href="/2015/10/26/why-is-nodejs-crashing-a-deep-dive-into-a-tricky-bug/">a bug that caused Node.js to crash when given invalid base64</a>. That post was already rather long, so I didn’t explain how one of our back-end services was getting invalid base64. Today, I satisfy everyone’s curiosity.</p> <h2 id="the-key-clue">The Key Clue</h2> <p>After deploying a patched Node.js, we added code to our services to detect and log invalid base64. Tailing the logs showed messages like this:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">Invalid base64 for 5827/94 Screen Shot 2015-10-03 at 23.59.15.png: �PNG\r\n\u001a\n\u0000\u0000\u0000\rIHDR\u0000\u0000\u0005�\u0000\u0000\u00028\b\u0006\u0000\u0000\u0000�wo�\u0000\u0000\f\u001aiCCPICC Profile\u0000\u0000H��W\u0007XS�\u0016�[R\b\t-\u0010\u0001)�7Az�\u001a:\bH\u0007\u001b!\t\u0010J\f��bG\u0016\u0015\\\u000b\*�(\*...</code></pre></figure> <p>As soon as I saw that, I knew what the problem was. The line noise you see is a <a href="https://nodejs.org/api/buffer.html">Node Buffer</a> (in this case a <a href="https://en.wikipedia.org/wiki/Portable_Network_Graphics">PNG</a>) that has been run through <code>.toString()</code>. To get base64, one needs to use <code>buffer.toString("base64")</code>. Somewhere in our codebase, we were missing an encoding parameter. But where, and why?</p> <h2 id="our-service-architecture">Our Service Architecture</h2> <p>To understand the issue, one also needs to know a little about our service architecture. Here is an extremely ornate diagram:</p> <pre style="font-size: 10px; overflow-wrap: none;"> Front-end servers Master Slaves +-------------+ +---------+ +--------+ +-------------&gt; |httpd | |colab | +----------&gt; |colab | &lt;-----+ +-------------&gt; |colabalancer | +----+----+------&gt; | | |\ | | | | +-------------+ / / +---------+ | \ +--------+ | | / / | \ | | +-------------+ / / | \ +--------+ | Internet +-------------&gt; |httpd | / / | ------&gt; |colab | &lt;-----+ +-------------&gt; |colabalancer | +----/--------------------------\---------&gt; | | | | +-------------+ / \ +--------+ | | / \ | | +-------------+ / \ +--------+ | +-------------&gt; |httpd | / ------&gt; |colab | &lt;-----+ +-------------&gt; |colabalancer | +-----------------------------------------&gt; | | +-------------+ +--------+ </pre> <p>All clients connect to our front-end servers. If the request is HTTP(S) (port 80 or 443), Apache httpd handles it. If it’s our protocol (port 3448), our colabalancer service handles it. Also, if the HTTP(S) request is a <a href="https://en.wikipedia.org/wiki/WebSocket">websocket</a>, httpd proxies it to colabalancer.</p> <p>When a client sends its auth info (api key, secret, workspace, etc), the colabalancer asks colab master, “Which slave has workspace X?” When the master responds, the balancer connects to that slave and pipes data between it and the client. The master also makes sure that there are multiple up-to-date copies of each workspace. If a workspace’s replication count is low, it tells a slave to copy the workspace from another slave. That way if a server suffers a hardware failure, everyone’s data is safe.</p> <h2 id="our-protocol">Our Protocol</h2> <p>All Floobits clients use our <a href="https://floobits.com/protocol">Realtime Differential Synchronization Protocol (RDSP)</a>. Colab slaves use the same protocol to replicate data amongst each other.</p> <p>You can read more about our protocol <a href="https://floobits.com/protocol">here</a>, but the key point is that the current version of our protocol uses JSON, which isn’t binary safe. Originally, we didn’t think it would be necessary to synchronize binary files. After all, who edits binary files in Sublime Text, Vim, or Emacs? But users wanted it, so we implemented it. To get around JSON’s limitation, binary files are base64 encoded. Since earlier versions of our plugins didn’t understand this change to our protocol, we added a <code>supported_encodings</code> field to our auth frame. If that field doesn’t exist, we default to <code>utf-8</code> as the only supported encoding.</p> <p>Now guess which field was accidentally removed in our colab client code. That’s right, <code>supported_encodings</code>. :(</p> <h2 id="piecing-it-together">Piecing it Together</h2> <p>Here’s how the whole mess went down, step by step:</p> <ol> <li>Colab master notices workspace 123 has a low replication count. Master picks candidates based on load and disk usage. It then tells slave01, “Fetch workspace 123 from slave00.”</li> <li>slave01 connects to slave00. It sends no <code>supported_encodings</code>, so slave00 thinks, “Oh. This client only supports utf8.”</li> <li>After authing and receiving <code>room_info</code> for workspace 123, slave01 sees that its copy of buffer 456 is out of date. It tells slave00, “Give me buffer 456”</li> <li>Unfortunately, buffer 456 is binary. Slave00 dutifully sends the utf8-encoded (and now corrupted) buffer to 01.</li> <li>Slave01 tries to decode the utf8-encoded buffer as if it were base64.</li> <li>Node.js crashes due to a bug in its base64 decoder.</li> </ol> <p>It may not sound too complicated when put that way, but troubleshooting this issue was very difficult. Because Node.js was crashing, most of our diagnostic and debugging tools were useless. There was no exception to catch, no error callback, no debug port we could attach to. As <a href="/2015/10/26/why-is-nodejs-crashing-a-deep-dive-into-a-tricky-bug/">our previous post describes</a>, it took a lot of thought, logging, and luck to track down the crash.</p> <h2 id="conclusion">Conclusion</h2> <p>Looking back, there were many ways we could have avoided this bug: better tests, more in-depth code reviews, static typing, etc. I think the real lesson to learn is more specific: We shouldn’t have used our own protocol for back-end replication. While it is more efficient —and an interesting technical problem to solve— it wasn’t worth the extra complexity and potential bugs. We could have saved time and sanity building the same system with a battle-hardend system like <a href="http://cassandra.apache.org/">Cassandra</a>.</p> <p>So if you’re ever thinking of making your own distributed data store, perhaps… reconsider.</p> Why is Node.js Crashing? A Deep Dive into a Tricky Bug 2015-10-26T09:59:01-07:00 https://news.floobits.com/2015/10/26/why-is-nodejs-crashing-a-deep-dive-into-a-tricky-bug/ <p>In early September, we started seeing one of our backend services exit with the following error:</p> <pre><code>2015-09-10_23:28:11.75413 node: ../src/node_buffer.cc:226: v8::MaybeLocal&lt;v8::Object&gt; node::Buffer::New(v8::Isolate*, v8::Local&lt;v8::String&gt;, node::encoding): Assertion `(data) != (nullptr)' failed. </code></pre> <p>Typically, Node.js exits with a JavaScript stack trace. This error was at a lower level. Node.js was dying because of an assertion in its <a href="https://nodejs.org/api/buffer.html">Buffer</a> code. At worst, creating a new Buffer should throw an exception. This was a crash. This was a Node.js bug.</p> <!--more--> <p>The first thing I did was look at the code containing the assertion error. This was in <a href="https://github.com/nodejs/node/blob/v4.2.1/src/node_buffer.cc#L225"><code>node_buffer.cc</code> on line 225</a>. I’ve included the entire <code>Buffer::New()</code> constructor below:</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="lineno">209</span> <span class="n">MaybeLocal</span><span class="o">&lt;</span><span class="n">Object</span><span class="o">&gt;</span> <span class="n">New</span><span class="p">(</span><span class="n">Isolate</span><span class="o">*</span> <span class="n">isolate</span><span class="p">,</span> <span class="lineno">210</span> <span class="n">Local</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">string</span><span class="p">,</span> <span class="lineno">211</span> <span class="k">enum</span> <span class="n">encoding</span> <span class="n">enc</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">212</span> <span class="n">EscapableHandleScope</span> <span class="n">scope</span><span class="p">(</span><span class="n">isolate</span><span class="p">);</span> <span class="lineno">213</span> <span class="lineno">214</span> <span class="kt">size_t</span> <span class="n">length</span> <span class="o">=</span> <span class="n">StringBytes</span><span class="o">::</span><span class="n">Size</span><span class="p">(</span><span class="n">isolate</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">enc</span><span class="p">);</span> <span class="lineno">215</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">malloc</span><span class="p">(</span><span class="n">length</span><span class="p">));</span> <span class="lineno">216</span> <span class="lineno">217</span> <span class="k">if</span> <span class="p">(</span><span class="n">data</span> <span class="o">==</span> <span class="k">nullptr</span><span class="p">)</span> <span class="lineno">218</span> <span class="k">return</span> <span class="n">Local</span><span class="o">&lt;</span><span class="n">Object</span><span class="o">&gt;</span><span class="p">();</span> <span class="lineno">219</span> <span class="lineno">220</span> <span class="kt">size_t</span> <span class="n">actual</span> <span class="o">=</span> <span class="n">StringBytes</span><span class="o">::</span><span class="n">Write</span><span class="p">(</span><span class="n">isolate</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">length</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">enc</span><span class="p">);</span> <span class="lineno">221</span> <span class="n">CHECK</span><span class="p">(</span><span class="n">actual</span> <span class="o">&lt;=</span> <span class="n">length</span><span class="p">);</span> <span class="lineno">222</span> <span class="lineno">223</span> <span class="k">if</span> <span class="p">(</span><span class="n">actual</span> <span class="o">&lt;</span> <span class="n">length</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">224</span> <span class="n">data</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">realloc</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">actual</span><span class="p">));</span> <span class="lineno">225</span> <span class="hll"> <span class="n">CHECK_NE</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">);</span> </span><span class="lineno">226</span> <span class="p">}</span> <span class="lineno">227</span> <span class="lineno">228</span> <span class="n">Local</span><span class="o">&lt;</span><span class="n">Object</span><span class="o">&gt;</span> <span class="n">buf</span><span class="p">;</span> <span class="lineno">229</span> <span class="k">if</span> <span class="p">(</span><span class="n">New</span><span class="p">(</span><span class="n">isolate</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">actual</span><span class="p">).</span><span class="n">ToLocal</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">))</span> <span class="lineno">230</span> <span class="k">return</span> <span class="n">scope</span><span class="p">.</span><span class="n">Escape</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span> <span class="lineno">231</span> <span class="lineno">232</span> <span class="c1">// Object failed to be created. Clean up resources.</span> <span class="lineno">233</span> <span class="n">free</span><span class="p">(</span><span class="n">data</span><span class="p">);</span> <span class="lineno">234</span> <span class="k">return</span> <span class="n">Local</span><span class="o">&lt;</span><span class="n">Object</span><span class="o">&gt;</span><span class="p">();</span> <span class="lineno">235</span> <span class="p">}</span></code></pre></figure> <p>Somehow, <code>data</code> was <code>NULL</code>. Thinking the <code>realloc()</code> on line 224 might have failed, I double-checked memory usage on the servers. They were not in danger of reaching any limits, and crashes didn’t seem to depend on memory usage. The service crashed while using 1GB of RAM just as often as it did while using 100MB. Dang. Not an easy fix. It was a slim hope anyways. Modern OSes don’t return <code>NULL</code> from <code>malloc()</code> and friends.<sup><a href="#ref_1">[1]</a></sup></p> <p>The next thing I did was <code>man realloc</code>, to try and figure out how it could return <code>NULL</code>. Except for an out-of-memory condition, it wasn’t possible. Even passing <code>NULL</code> to <code>realloc()</code> returned a usable chunk of memory:</p> <blockquote> <p>If ptr is NULL, realloc() is identical to a call to malloc() for size bytes. If size is zero and ptr is not NULL, a new, minimum sized object is allocated and the original object is freed.</p> </blockquote> <p>There was simply no way for <code>Buffer::New()</code>, executed sequentially, to fail in this way. Therefore (I reasoned), the bug must be another thread modifying shared state. Probably a hard-to-reproduce race condition. Ugh. Still, I needed to fix the issue. Desiring to know more, I tried to build a reproducible test case. Annoyingly, I could only trigger the crash in production and staging, and only when copying lots of data between instances of the service. Having a lot of other stuff to do, I mitigated the issue by reducing the peak rate at which the service copied data.</p> <p>A month later, Matt finally got tired of seeing crash emails. We paired to try and find the underlying cause. When I was explaining the issue to Matt, I pointed out that <code>realloc()</code> never returns <code>NULL</code>. He double-checked the docs and disagreed. When he linked to the manpage describing <code>realloc()</code>’s behavior, it said:</p> <blockquote> <p>If size was equal to 0, either NULL or a pointer suitable to be passed to free() is returned.</p> </blockquote> <p>Wait, what?! It turned out that I had run <code>man realloc</code> on my mac, while he had googled “realloc” and clicked on the first result. That result described <code>realloc()</code> on Linux. The two behaved differently, and that difference was crucial. Again, here are the relevant lines from <code>Buffer::New</code>:</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="lineno">220</span> <span class="kt">size_t</span> <span class="n">actual</span> <span class="o">=</span> <span class="n">StringBytes</span><span class="o">::</span><span class="n">Write</span><span class="p">(</span><span class="n">isolate</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">length</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">enc</span><span class="p">);</span> <span class="lineno">221</span> <span class="n">CHECK</span><span class="p">(</span><span class="n">actual</span> <span class="o">&lt;=</span> <span class="n">length</span><span class="p">);</span> <span class="lineno">222</span> <span class="lineno">223</span> <span class="k">if</span> <span class="p">(</span><span class="n">actual</span> <span class="o">&lt;</span> <span class="n">length</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">224</span> <span class="n">data</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">realloc</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">actual</span><span class="p">));</span> <span class="lineno">225</span> <span class="n">CHECK_NE</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">);</span> <span class="lineno">226</span> <span class="p">}</span></code></pre></figure> <p>If <code>realloc()</code> returns null, <code>actual</code> must be 0. If <code>actual</code> is 0, <code>StringBytes::Write()</code> must have returned 0. But to get to line 220, <code>length</code> must be greater than 0. How could this happen? Well, <code>length</code> is only set on line 214:</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="lineno">214</span> <span class="kt">size_t</span> <span class="n">length</span> <span class="o">=</span> <span class="n">StringBytes</span><span class="o">::</span><span class="n">Size</span><span class="p">(</span><span class="n">isolate</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">enc</span><span class="p">);</span></code></pre></figure> <p>So <code>StringBytes::Size()</code> thinks the buffer is a certain size, but <code>StringBytes::Write()</code> disagrees or fails in some way. To confirm this hypothesis, Matt and I added a <code>printf()</code> before the <code>realloc()</code>:</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="lineno">220</span> <span class="kt">size_t</span> <span class="n">actual</span> <span class="o">=</span> <span class="n">StringBytes</span><span class="o">::</span><span class="n">Write</span><span class="p">(</span><span class="n">isolate</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">length</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">enc</span><span class="p">);</span> <span class="lineno">221</span> <span class="n">CHECK</span><span class="p">(</span><span class="n">actual</span> <span class="o">&lt;=</span> <span class="n">length</span><span class="p">);</span> <span class="lineno">222</span> <span class="lineno">223</span> <span class="k">if</span> <span class="p">(</span><span class="n">actual</span> <span class="o">&lt;</span> <span class="n">length</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">224</span> <span class="n">printf</span><span class="p">(</span><span class="s">&quot;actual: %u length: %u</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">,</span> <span class="n">actual</span><span class="p">,</span> <span class="n">length</span><span class="p">);</span> <span class="lineno">225</span> <span class="n">data</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">realloc</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">actual</span><span class="p">));</span> <span class="lineno">226</span> <span class="n">CHECK_NE</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">);</span> <span class="lineno">227</span> <span class="p">}</span></code></pre></figure> <p>I deployed this custom Node.js build to staging, and soon saw crashes. The crashes were preceded by lines such as:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">2015-10-17_19:22:45.89086 actual: 0 length: 11518</code></pre></figure> <p>Yahtzee! We were on the right track. Now how could <code>StringBytes::Write()</code> return 0? We both suspected <a href="https://en.wikipedia.org/wiki/Base64">base64</a>-encoded buffers. Delving into <a href="https://github.com/nodejs/node/blob/v4.2.1/src/string_bytes.cc"><code>string_bytes.cc</code></a>, we saw that <code>StringBytes::Size()</code> called <code>base64_decoded_size()</code>, which called <code>base64_decoded_size_fast()</code>, which basically returned <code>length / 4 * 3</code>.<sup><a href="#ref_1">[2]</a></sup> At no point did any of these methods check for valid base64 encoded data. They didn’t strip whitespace or invalid characters. They just multiplied by 0.75.</p> <p>It was a different story for <code>StringBytes::Write()</code>. That function called <code>base64_decode()</code>, which called <code>base64_decode_fast()</code>, which could return early with invalid base64 data. In that case, <code>base64_decode()</code> falls back to calling <code>base64_decode_slow()</code>. Let’s take a look at that function, which starts at <a href="https://github.com/nodejs/node/blob/v4.2.1/src/string_bytes.cc#L167">line 167 of <code>string_bytes.cc</code></a>:</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="lineno">167</span> <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="n">TypeName</span><span class="o">&gt;</span> <span class="lineno">168</span> <span class="kt">size_t</span> <span class="n">base64_decode_slow</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">dst</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">dstlen</span><span class="p">,</span> <span class="lineno">169</span> <span class="k">const</span> <span class="n">TypeName</span><span class="o">*</span> <span class="n">src</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">srclen</span><span class="p">)</span> <span class="p">{</span> <span class="lineno">170</span> <span class="kt">uint8_t</span> <span class="n">hi</span><span class="p">;</span> <span class="lineno">171</span> <span class="kt">uint8_t</span> <span class="n">lo</span><span class="p">;</span> <span class="lineno">172</span> <span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="lineno">173</span> <span class="kt">size_t</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="lineno">174</span> <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span> <span class="lineno">175</span> <span class="cp">#define V(expr) \</span> <span class="lineno">176</span> <span class="cp"> while (i &lt; srclen) { \</span> <span class="lineno">177</span> <span class="cp"> const uint8_t c = src[i]; \</span> <span class="lineno">178</span> <span class="cp"> lo = unbase64\(c\); \</span> <span class="lineno">179</span> <span class="cp"> i += 1; \</span> <span class="lineno">180</span> <span class="cp"> if (lo &lt; 64) \</span> <span class="lineno">181</span> <span class="cp"> break; /\* Legal character. \*/ \</span> <span class="lineno">182</span> <span class="hll"><span class="cp"> if (c == &#39;=&#39;) \</span> </span><span class="lineno">183</span> <span class="hll"><span class="cp"> return k; \</span> </span><span class="lineno">184</span> <span class="cp"> } \</span> <span class="lineno">185</span> <span class="cp"> expr; \</span> <span class="lineno">186</span> <span class="cp"> if (i &gt;= srclen) \</span> <span class="lineno">187</span> <span class="cp"> return k; \</span> <span class="lineno">188</span> <span class="cp"> if (k &gt;= dstlen) \</span> <span class="lineno">189</span> <span class="cp"> return k; \</span> <span class="lineno">190</span> <span class="cp"> hi = lo;</span> <span class="lineno">191</span> <span class="n">V</span><span class="p">(</span><span class="cm">/* Nothing. */</span><span class="p">);</span> <span class="lineno">192</span> <span class="n">V</span><span class="p">(</span><span class="n">dst</span><span class="p">[</span><span class="n">k</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">((</span><span class="n">hi</span> <span class="o">&amp;</span> <span class="mh">0x3F</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="n">lo</span> <span class="o">&amp;</span> <span class="mh">0x30</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">4</span><span class="p">));</span> <span class="lineno">193</span> <span class="n">V</span><span class="p">(</span><span class="n">dst</span><span class="p">[</span><span class="n">k</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">((</span><span class="n">hi</span> <span class="o">&amp;</span> <span class="mh">0x0F</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">4</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="n">lo</span> <span class="o">&amp;</span> <span class="mh">0x3C</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">2</span><span class="p">));</span> <span class="lineno">194</span> <span class="n">V</span><span class="p">(</span><span class="n">dst</span><span class="p">[</span><span class="n">k</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">((</span><span class="n">hi</span> <span class="o">&amp;</span> <span class="mh">0x03</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">6</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="n">lo</span> <span class="o">&amp;</span> <span class="mh">0x3F</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">0</span><span class="p">));</span> <span class="lineno">195</span> <span class="cp">#undef V</span> <span class="lineno">196</span> <span class="p">}</span> <span class="lineno">197</span> <span class="n">UNREACHABLE</span><span class="p">();</span> <span class="lineno">198</span> <span class="p">}</span></code></pre></figure> <p>This macro-fied code may be a little hard to follow, but the behavior we care about is straightforward. Look at lines 182 and 183. Any “<code>=</code>” in the data causes the function to return early. It doesn’t matter if <code>src</code> is a megabyte. If the first character is “<code>=</code>”, <code>k</code> is still zero when line 183 is hit. Once we figured that out, it wasn’t too hard to reproduce the issue in a line of JavaScript. Try this with Node.js (or io.js) from v3.0.0 to v4.2.1:</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">ggreer</span><span class="err">@</span><span class="nx">lithium</span><span class="o">:~%</span> <span class="nx">node</span> <span class="o">&gt;</span> <span class="k">new</span> <span class="nx">Buffer</span><span class="p">(</span><span class="s2">&quot;=&quot;</span> <span class="o">+</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mi">10000</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="s2">&quot;A&quot;</span><span class="p">),</span> <span class="s2">&quot;base64&quot;</span><span class="p">);</span> <span class="nx">node</span><span class="o">:</span> <span class="p">..</span><span class="o">/</span><span class="nx">src</span><span class="o">/</span><span class="nx">node_buffer</span><span class="p">.</span><span class="nx">cc</span><span class="o">:</span><span class="mi">225</span><span class="o">:</span> <span class="nx">v8</span><span class="o">::</span><span class="nx">MaybeLocal</span><span class="o">&lt;</span><span class="nx">v8</span><span class="o">::</span><span class="nb">Object</span><span class="o">&gt;</span> <span class="nx">node</span><span class="o">::</span><span class="nx">Buffer</span><span class="o">::</span><span class="nx">New</span><span class="p">(</span><span class="nx">v8</span><span class="o">::</span><span class="nx">Isolate</span><span class="o">*</span><span class="p">,</span> <span class="nx">v8</span><span class="o">::</span><span class="nx">Local</span><span class="o">&lt;</span><span class="nx">v8</span><span class="o">::</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">node</span><span class="o">::</span><span class="nx">encoding</span><span class="p">)</span><span class="o">:</span> <span class="nx">Assertion</span> <span class="err">`</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">!=</span> <span class="p">(</span><span class="nx">nullptr</span><span class="p">)</span><span class="err">&#39;</span> <span class="nx">failed</span><span class="p">.</span> <span class="nx">zsh</span><span class="o">:</span> <span class="nx">abort</span> <span class="p">(</span><span class="nx">core</span> <span class="nx">dumped</span><span class="p">)</span> <span class="nx">node</span> <span class="nx">ggreer</span><span class="err">@</span><span class="nx">lithium</span><span class="o">:~%</span></code></pre></figure> <p>Armed with a one-liner crash, I <a href="https://github.com/nodejs/node/issues/3496">reported the issue</a> and described how I thought it was breaking. It only took a day for <a href="https://github.com/bnoordhuis">Ben Noordhuis</a> to fix the bug in master. Node.js v4.2.2 and v5.0.0 shipped with the fix. Mission accomplished!</p> <p>Except… we didn’t answer some important questions: Where was this invalid base64 coming from? Why was our back-end service processing it? Those answers are in <a href="/2015/11/02/bad-base64-a-not-so-tricky-bug/">the next post</a>.</p> <hr /> <p>Thanks to Matt Kaniaris, Ben Noordhuis, and the rest of the Node.js team for their help.</p> <ol> <li> <p><span id="ref_1"></span> Even when a process asks for more memory than is available, modern OSes return a usable pointer. Only when the memory is accessed will the OS jump into action and free memory by <a href="http://linux-mm.org/OOM_Killer">killing processes</a>.</p> </li> <li> <p><span id="ref_2"></span> Why <code>length / 4 * 3</code> instead of <code>length * 0.75</code> or <code>(length * 3) / 4</code>? This is C++, so the former requires a type conversion to float or double, followed by rounding. The latter could overflow if <code>length</code> is greater than <code>SIZE_MAX / 3</code>.</p> </li> </ol> Developing Atom Packages, Part 2: So Much Potential, So Many Bugs 2015-10-14T08:58:38-07:00 https://news.floobits.com/2015/10/14/developing-atom-plugins-so-much-potential-so-many-bugs/ <p>Atom has improved drastically since its first release, but it’s still not perfect. While building <a href="https://github.com/Floobits/floobits-atom">our Atom package</a>, we ran into quite a few bugs. Some have been fixed, but many still persist. <a href="/2015/10/12/developing-atom-plugins-on-the-bleeding-edge/">Previously</a>, I discussed Atom and its progress. That post addressed larger, broader issues. This post gets into specifics. <em>Lots</em> of specifics.</p> <!--more--> <h3 id="yay-its-a-browser-oh-no-its-a-browser">Yay, it’s a Browser. Oh no, it’s a Browser!</h3> <p>Atom is based on Chromium. While this does have the advantage of allowing for packages to be written with JavaScript and HTML, it also causes some unexpected problems. For example: In a browser, submitting a form causes a page reload. Atom behaves the same. Without a handler on the form that calls <code>event.preventDefault()</code>, Atom will reload the <em>entire</em> window, losing state and breaking many UI elements.</p> <p>Another issue with Atom being a “sorta-browser” reveals itself when one tries to load remote content, such as a website. Iframes are the obvious choice, but many sites <a href="https://en.wikipedia.org/wiki/Framekiller">kill frames</a> to prevent <a href="https://en.wikipedia.org/wiki/Clickjacking">clickjacking</a>. The only solution is to use the Chrome-specific <a href="https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md">webview tag</a>. Unlike frames, webviews in Atom can’t use <code>postMessage()</code> to communicate with the parent window. Instead, the parent has to bind to <a href="https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md#event-console-message">the console-message event</a>. This receives data from <code>console.log()</code> calls in the webview. See <a href="https://github.com/Floobits/floobits-atom/blob/master/templates/webview.js#L23">our web view template</a> for an example. This solution worked for us, but it’s not a cure-all. If you can’t add <code>console.log()</code> calls to the pages loaded in the webview, there’s no way to get messages out.</p> <p>If your package has any visible UI, you’re also likely to run into styling conflicts. All Atom packages can include CSS or LESS, and all of their styles are applied globally. In addition, Atom loads a subset of <a href="http://getbootstrap.com/">Bootstrap</a> for its own styling. To improve load time, Atom’s Bootstrap is CSS, not LESS. That means you won’t be able to <code>@import</code> it for your own package’s use.</p> <h3 id="packaging">Packaging</h3> <p>Compared to other editors, Atom’s packaging system is <em>amazing</em>. Most editors don’t even come with a package manager. Instead, users are forced to install third-party managers such as <a href="https://github.com/wbond/package_control">Package Control</a> or <a href="https://github.com/VundleVim/Vundle.vim">Vundle</a>. Atom’s package manager is great for users <em>and</em> developers. Users can easily find, install, and update packages. Developers can specify dependencies using <a href="https://www.npmjs.com/">npm</a>’s <code>package.json</code> format. Packages can even <a href="https://atom.io/docs/latest/behind-atom-interacting-with-other-packages-via-services">depend on other packages</a>. For example, our Atom package depends on <a href="https://atom.io/packages/term3">Term3</a>. That allowed us to avoid copying a bunch of terminal-related code into our main package’s source tree. It also means user settings for Term3 are always consistent. With luck, we’ll be able to get our changes merged into <a href="https://github.com/f/atom-term2">Term2</a> and depend on it instead. If not for Atom’s package management, we would have a complete fork with polluted history. The prospect of merging would be all but hopeless.</p> <p>Of course, like many parts of Atom, packaging does have a few rough edges. <a href="https://atom.io/docs/api/v1.0.19/PackageManager#instance-enablePackage">Packages can load other packages</a>… most of the time. If a package uses <code>activationCommands</code>, <a href="https://discuss.atom.io/t/cant-activate-package-in-specs/13672/9">you’re out of luck</a>. <a href="https://discuss.atom.io/t/can-you-force-the-activation-of-another-package/10885/18">There are a few undocumented workarounds</a>, but these hacky solutions are likely to break in minor Atom updates. Though packages can declare other packages as service dependencies, and they can (sometimes) load them, the dependent services will not be automatically installed. Users have to manually install them.</p> <h3 id="the-view-system">The View System</h3> <p>Atom’s view system has been a long series of misadventures. Early on, Atom recommended <a href="https://github.com/atom-archive/space-pen">Space Pen</a>, which was little more than a wrapper around <a href="https://jquery.com/">JQuery</a>. Then, <a href="http://blog.atom.io/2014/07/02/moving-atom-to-react.html">Atom moved to React</a>. Because package writers coded their packages against Atom’s React, Atom is forced to stay on <a href="https://www.npmjs.com/package/react-atom-fork">an old fork</a> (multiple versions of React <a href="https://github.com/facebook/react/issues/2402">can’t run in the same context</a>). It’s unclear what will happen to Atom’s React, but its future doesn’t seem promising. Atom devs have been slowly removing React from core parts of Atom. More recently, Atom has started to implement parts of their visible UI in <a href="https://github.com/atom/atom/issues/5756">special-purpose HTML tags</a>. This doesn’t seem to be the end-game for the view system though. The discussion around this topic has yet to nail down a solution.</p> <p>To summarize:</p> <table style="width: 450px;"> <thead> <th>Atom View API</th> <th>Status</th> </thead> <tbody> <tr> <td>Space Pen</td> <td><a href="https://github.com/atom/atom-space-pen-views">Deprecated</a></td> </tr> <tr> <td>React</td> <td><a href="https://github.com/jgebhardt/react-for-atom#a-single-instance-of-react">Deprecated</a></td> </tr> <tr> <td>Special DOM elements</td> <td><a href="https://github.com/atom/atom/issues/3752#issuecomment-60645402">Maybe deprecated soon?</a></td> </tr> </tbody> </table> <p>It’s unclear what’s next for Atom’s view API, but hopefully the community settles on something.</p> <p>The API for managing windows is also a mess. For example, <code>atom.open({'pathsToOpen': ['~/code/example'], 'newWindow': false});</code> <a href="https://github.com/atom/atom/issues/5138">opens a new window</a>, despite the fact that <code>newWindow</code> is false. Annoyingly, <code>atom.open()</code> can also <em>close</em> current windows. The exact circumstances in which this happens are (so far) unpredictable. This bug is bad enough that we’ve had to add a disclaimer in our Atom package.</p> <h3 id="miscellaneous">Miscellaneous</h3> <p>Atom’s API and internals have changed significantly since its first release, but a few annoyances have persisted. A big one for us has been resize detection. It’s very useful to know when a pane was resized. The only way to do that right now is <a href="https://github.com/abe33/atom-utils#resizedetection">inefficient polling</a>. (See the implementation <a href="https://github.com/abe33/atom-utils/blob/master/src/mixins/resize-detection.coffee#L25">here</a>).</p> <p>A few pernicious issues stem from faults in Atom’s architecture. Packages have no isolation, so it’s easy for them to step on each other’s toes. Styling conflicts are the most common version of this. Much of Atom itself is implemented as packages, so this lack of isolation means that misbehaving packages can hang or crash Atom. Fixing this is an immense task, as it requires completely changing the plugin architecture. Even if Atom goes down that road, there’s no perfect solution. As Chrome extensions show, isolating packages has its own disadvantages.</p> <p>One thing that Atom has done a great job of is unbinding event handlers. Their <a href="https://atom.io/docs/api/v1.0.19/CompositeDisposable">disposable</a> objects make cleanup of event handlers trivial. Instead of forcing developers to keep track of bound functions, disposables have a <code>dispose</code> method that unbinds the event handler. Disposables can also be collected into a <code>CompositeDisposable</code>. I wish more JavaScript codebases used disposables.</p> <hr /> <p>This post may seem critical of Atom, but that’s not the case. I took the time criticize Atom because I care about it, and I want it to be better. I think Atom is a fine editor with a <em>ton</em> of potential. It has come a long way in a short time. Hopefully, Atom developers will use this post to improve it even more.</p> <!-- we duck type/mock an atom Pane as they don't expose one anywhere to load external html https://github.com/Floobits/floobits-atom/blob/master/templates/pane.coffee --> Developing Atom Plugins, Part 1: On the Bleeding Edge 2015-10-12T22:16:22-07:00 https://news.floobits.com/2015/10/12/developing-atom-plugins-on-the-bleeding-edge/ <p>Back in February of 2014, GitHub <a href="http://blog.atom.io/2014/02/26/introducing-atom.html">announced their new editor: Atom</a>. We’ve followed Atom since it went public, and recently developed <a href="https://github.com/Floobits/floobits-atom">a Floobits plugin for it</a>. What follows are our impressions from the experience.</p> <h3 id="the-history-of-atom-in-30-seconds">The History of Atom in 30 Seconds</h3> <p>At the time of its release, Atom was slow, buggy, and lacked basic features. Many wrote it off it as cheap imitation of <a href="https://www.sublimetext.com/">Sublime Text</a>. In the subsequent 18 months, Atom has improved remarkably. The <a href="http://blog.atom.io/2015/06/25/atom-1-0.html">recent 1.0 release</a> is a powerful, extensible editor suited for everyday use. Atom still isn’t as fast or as stable as Sublime Text, but it’s catching up quickly. Or, to put it more accurately: Sublime Text development has stagnated. Since the first release of Atom, Sublime Text 2 has had <em>zero</em> releases. Sublime Text 3 Beta has had three, one of which was a minor bug fix. Considering the difference in development speed, Atom will almost certainly improve faster than Sublime Text.</p> <h3 id="not-all-rainbows-and-unicorns">Not All Rainbows and Unicorns</h3> <p>That said, Atom’s journey hasn’t been entirely pleasant. Its development process is accurately described by Facebook’s slogan: “Move fast and break things.” Since its initial release, Atom’s API has <a href="https://atom.io/docs/v0.186.0/upgrading/upgrading-your-package">changed drastically</a>, breaking package compatibility multiple times. Even the <a href="http://blog.atom.io/2014/05/06/atom-is-now-open-source.html">license has changed</a>. Most of these changes have been for the better, but keeping up with them requires significant time and effort. Hopefully, things will settle down now that Atom is 1.x.</p> <h3 id="its-a-browser-sort-of">It’s a Browser… Sort Of</h3> <p>Many of Atom’s detractors point out that it’s based on a browser: Chromium. While this does increase resource usage and startup time, there are significant advantages to building on top of Chromium:</p> <ul> <li>Fewer cross-platform issues. Chromium already works well on OS X, Windows, and Linux.</li> <li>Plugins are written using JavaScript, HTML, and CSS. Web developers can quickly learn to extend Atom.</li> <li>Tracking down errors in Atom is much easier than other editors, thanks to Chromium’s debugger. Only IntelliJ has comparable self-debugging abilities.</li> <li>Chromium has browser technologies such as <a href="https://en.wikipedia.org/wiki/WebRTC">WebRTC</a>, which aren’t available in any other editor or IDE. <a href="https://github.com/Floobits/floobits-atom">Floobits for Atom</a> uses WebRTC for video chat. It’s very nice.</li> </ul> <p>Leveraging a browser has advantages and disadvantages, but I think GitHub made the right choice.</p> <h3 id="a-few-missing-pieces">A Few Missing Pieces</h3> <p>The Atom devs have done a great job over the past 18 months, but a few conspicuous issues still linger. By far, the most glaring bug is binary safety. If you open a binary file with Atom and save it without making any changes, <a href="https://github.com/atom/node-pathwatcher/issues/62">the file will be corrupted</a>. The only other editor I know that does this is <a href="https://en.wikipedia.org/wiki/GNU_nano">nano</a>.</p> <p>Another glaring omission is Atom’s lack of GitHub integration. Some extensions are GitHub-specific, but the editor itself has no GitHub features. Built-in GitHub authentication would be useful for many extensions. Instead of setting your GitHub API key/secret in each plugin, Atom could provide it (with appropriate prompting, of course).</p> <p>Despite these shortcomings, I’ve found myself using Atom more and more. It’s become a very compelling alternative to Sublime Text.</p> <p><br /></p> <p>For a more technical discussion of Atom’s current issues, read <a href="/2015/10/14/developing-atom-plugins-so-much-potential-so-many-bugs/">part 2</a>.</p> <!-- advantages: javascript (always bet on js) ✓ https://discuss.atom.io/t/coffeescript---extends-vs-util-inherits-inheritance-in-js/2536 it's a browser ✓ webrtc ✓ its a browser, sorta: same origin policy/HSTS fucks loading stuff in iframes you can use a webview, but it doesn't honor postmessage events use console.message as a channel between plugin and webpage https://github.com/Floobits/floobits-atom/blob/master/templates/webview.js#L13 plugin conflicts: everyones CSS conflicts - actual problem in the wild I expect front end frameworks will conflict as people write IDE like plugins see https://github.com/facebook/react/issues/1939#issuecomment-50632807 posting forms refreshes the "page" (atom) if you don't preventdefault https://github.com/Floobits/floobits-atom/commit/9d478125c9b431f950146bbe644f1cac3fbc2b0e The API: the good: https://atom.io/docs/api/v1.0.7/Disposable#instance-dispose --> IntelliJ IDEA Plugin Development and Java 6 2015-08-27T14:20:00-07:00 https://news.floobits.com/2015/08/27/intellij-idea-plugin-development-and-java-6/ <p>Based on my experience of the last couple of years writing editor plugins for Floobits, I can say that IntelliJ IDEA’s plugin system is phenomenal. Extending IntelliJ IDEA is particularly stellar. Unlike most editors, IntelliJ’s plugin API gives you access to everything. The IDE architecture is very well designed. The core is <a href="https://github.com/JetBrains/intellij-community">open source</a> and easy to follow. And IntelliJ plugins have all of Java’s libraries available. It is quite amazing.</p> <p>There is a lot to rave about, but one thing that isn’t great is Java versioning. IntelliJ IDEA is a cross-platform Java-based editor, and its plugins must share the JRE that the editor runs on. The version of Java you choose for writing your plugin must be compatible with your target audience’s installed Java. If the user does not have the correct version of Java, your plugin will cause a nasty error at start time and your plugin will be automatically disabled. There is no way to specify in the plugin configuration file (<code>plugin.xml</code>) which version of Java you support. That means there’s no way to prevent people from installing something that will explode on them. If a user encounters this error, there’s no easy way to explain what happened or what to do about it.</p> <p>The solution recommended by JetBrains is to use Java 6 for plugin IntelliJ IDEA development. But Java 6 is old. It’s so old that Oracle <a href="http://www.oracle.com/technetwork/java/archive-139210.html">no longer provides security updates</a>. Compared to Java 8 (or even 7), Java 6 is terrible to work with. There are no <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html">lambdas</a>. There are none of the <a href="https://en.wikipedia.org/wiki/Non-blocking_I/O_%28Java%29">new <code>nio</code> APIs</a> to reduce the pain of working with file I/O and threads.</p> <p>Most people are running at least Java 7 now, so I looked into why JetBrains recommends Java 6. It turns out that for OS X users, Java 6 is what most people have, if they even have Java at all. Apple used to develop their own version of Java for OS X, but they gave it up, and the last version they supported was Java 6. Apple <a href="https://support.apple.com/downloads/java">still makes this old version available</a>, and it still gets security updates, but Apple refers to it as ‘legacy’ and has no plans to support any new versions of Java. Oracle does supply newer versions of Java that are OS X compatible, but they have a severe problem: The font-rendering is <em>atrocious</em> on OS X. This is a huge problem for an IDE! Blurry, pixelated, aliased fonts are not what you want to stare at for hours at end. Oracle is looking to fix this for newer versions of Java, but for now we are stuck with Java 6.</p> <p>Given the state of Java on OS X, JetBrains has done a great job. Recently, they forked OpenJDK, fixed the font-rendering on OS X, and built versions of each of their IntelliJ IDEA based editors that <a href="https://confluence.jetbrains.com/display/IntelliJIDEA/Previous+IntelliJ+IDEA+Releases">include this version of Java</a>. Unfortunately, this download is not the default. Discovering it requires clicking through a link for “previous releases.” In my opinion, this version of IntelliJ IDEA should be the default version for all OS X users and the version without a JRE should be an alternate download. Sadly, even if this was the case, there would still be many Java 6 installs. For now, plugins must be written to match the lowest common denominator: the JRE for Java 6.</p> <p>I <a href="https://devnet.jetbrains.com/message/5548947">asked about Java version detection</a> on the JetBrains plugin development forum, and <a href="https://devnet.jetbrains.com/message/5548962#5548962">was told</a>:</p> <blockquote> <p>There is no possibility to mark a plugin in this way (and we don’t plan to provide one). If you want to use lambdas and new things, please consider writing your plugin in Kotlin, which does have lambdas and new things, but compiles to regular Java 6 bytecode.</p> </blockquote> <p>Our IntelliJ plugin is already written in Java, but if we were to build it today, we’d probably use <a href="http://kotlinlang.org/">Kotlin</a>. Kotlin is a pretty amazing programing language. It provides many modern programming language concepts, such as lambdas, null-pointer saftey, and first-class functions. It does this without making it difficult to use Java libraries and code. Though using Kotlin still wouldn’t give us access to the <code>nio</code> APIs, the situation would be better.</p> <p>In order to bring Floobits to as many editors and platforms possible, we have to endure the pain that is editor plugin development. Whenever we build plugins, we run into issues that most developers would walk away from. We don’t have that option, so Java 6 it is. Having said that, IntelliJ IDEA actually has the best plugin development API out there. This post doesn’t mean to detract from that.</p> <p>If you’re curious, you can <a href="https://github.com/Floobits/floobits-intellij">see our IntelliJ plugin’s code on GitHub</a>.</p> Sublime Text's Plugin API: It's Python... Sort Of 2015-08-17T15:37:18-07:00 https://news.floobits.com/2015/08/17/sublime-text-plugin-api-its-python-sort-of/ <blockquote> <p>“Sort of” is such a harmless thing to say… sort of. It’s just a filler. Sort of… it doesn’t really mean anything. But after certain things, sort of means everything. Like… after “I love you”… or “You’re going to live.”</p> </blockquote> <p>— <a href="https://en.wikipedia.org/wiki/Demetri_Martin">Demetri Martin</a></p> <p><a href="http://www.sublimetext.com/">Sublime Text</a> is my editor of choice. It’s powerful, flexible, and accessible. But as much as I like it, Sublime Text has one glaring problem: its plugin API is Python… sort of. That “sort of” is responsible for much frustration and annoyance.</p> <!--more--> <p>For most devs most of the time, writing Sublime Text plugins is scarcely different from writing normal Python. Just import some modules, define your <a href="https://www.sublimetext.com/docs/3/api_reference.html#sublime_plugin.ApplicationCommand">Sublime commands</a>, and you’re golden. At Floobits, we learned the hard way that this isn’t always the case. Two years ago, after months of extensive development and testing, we finally judged our Sublime Text plugin ready. We released it to the world… only to suffer a deluge of complaints. Many on Windows and Linux couldn’t use our plugin. Merely activating our plugin raised exceptions on their systems.</p> <p>This confused us. Though we develop on Macs, the code should work fine everywhere. Except for a few well-documented edge cases, Python behaves the same on Windows, OS X, and Linux.</p> <p>Sadly, that’s not true for Sublime Text’s Python. The Python that ships with Sublime Text varies significantly in its capabilities. On some platforms and Sublime Text verisons, standard modules are broken. You can verify this yourself. Open Sublime Text 2 on Windows and run <code>import select</code> in the console:</p> <p><img src="/images/st2_win_select.png" /></p> <p>Or if you’re on Linux, open Sublime Text 2 or 3, then try to <code>import ssl</code><sup><a href="#ref_1">[1]</a></sup>:</p> <p><img src="/images/st2_linux_ssl.png" /></p> <p>For Floobits, these errors were deal-breakers. Our plugins need <code>select()</code> for asynchronous network I/O. Our network protocol always uses encryption, necessitating the <code>ssl</code> module. For our purposes, these modules had no alternatives or substitutes. At the time, this realization was extremely disheartening. We racked our brains for solutions. It took a while, but we had an insight: Sublime’s Python might lack <code>ssl</code>, but the operating system’s Python should be fine. If we could hand encryption/decryption work to the OS’s Python, we’d be in business. Once we knew what to do, <a href="https://github.com/Floobits/floobits-sublime/pull/144">our solution</a> took only a week to code.</p> <p>Our users were happy, but the experience left me disillusioned. <a href="https://github.com/SublimeTextIssues/Core/issues/177">I reported the <code>import ssl</code> issue</a> almost two years ago, but it still hasn’t been fixed. At this point, I doubt it ever will be.</p> <ol> <li><span id="ref_1"></span> This may not error for users of <a href="https://packagecontrol.io/">Package Control</a>, which can install its own ssl module.</li> </ol> Introducing Floobits Pro 2015-08-12T12:47:53-07:00 https://news.floobits.com/2015/08/12/introducing-floobits-pro/ <p>Today, we are proud to introduce <a href="https://floobits.com/pro/client">Floobits Pro</a>. Floobits Pro lets you to hire other developers to help you out on your project. Whether you just need another pair of eyes to double-check a function, or an expert to track down a race condition, we’ve got you covered.</p> <p>Signing up is easy. Go to our <a href="https://floobits.com/pro/client/signup-1">sign up form</a>, provide us with your billing information (you will not be charged until you complete a session), and tell us what you need. We’ll create a workspace where you can add files and collaborate.</p> <p>If you already have a Floobits account and you want to get help in an existing workspace, just click on the “Floobits Pro” button in the upper-right of the web editor.</p> <p><img src="/images/Screen Shot 2015-08-13 at 12.04.02.png" /></p> <p>Once you request help, we’ll alert the Pros who in turn choose to work with you. We provide you with a description of the technology they are familiar with, their feedback from previous sessions, and give you the option to chat before you make your decision of who to hire.</p> <!-- (show another screenshot?) --> <p>After you pick a Pro, they’ll immediately join your workspace and be ready to help. You can use all the tools that make Floobits great: work in your native editor, share terminals, and video chat using WebRTC or Google Hangouts. If for some reason it doesn’t work out, don’t worry. Cancel the session in the first five minutes and you won’t pay anything.</p> <p>One more thing: We’re giving all of our paying users an hour of free help. That’s right: If you are on a paid plan, we’ll help you with your code for free! Think of it as a thank-you for your support.</p> <p>We are very excited about Pro and hope you’ll give it a try. If you have any comments or suggestions, don’t hesitate to <a href="mailto:info@floobits.com">contact us</a>. If you are interested in becoming a Pro, <a href="https://floobits.com/signup/pro">apply here</a>. If you are concerned about the future for Floobits “Classic”, don’t be. We will continue to develop and support our plugins for native pair programming.</p> New Chat Feature: Floobot 2015-03-29T22:06:28-07:00 https://news.floobits.com/2015/03/29/new-chat-feature-floobot/ <p>If you chat in workspaces or our <a href="https://floobits.com/help/orgs#video_chat">organization chat</a>, you might notice that pasting URLs can cause a mysterious “Floobot” user to describe them. Don’t be startled, this is expected behavior. Similar to showing images in chat, Floobot helps you to get information about a URL without having to click on it.</p> <p>Floobot does more than just parse <code>&lt;title&gt;</code> tags. If the link is to a GitHub repo, it will mention the number of forks and stargazers. If the link is a tweet, it will give a summary, along with the number of favorites and retweets. Floobot also describes <a href="https://youtube.com/">YouTube</a> videos and <a href="https://news.ycombinator.com/">Hacker News</a> submissions. Basically, if you paste a URL, Floobot tries its best to give a one-line summary of what’s there.</p> <p>Here’s a screenshot of what Floobot typically looks like in org chat:</p> <p><a href="/images/Screen Shot 2015-03-30 at 13.18.10.png"><img src="/images/Screen Shot 2015-03-30 at 13.18.10.png" alt="Floobot in Floobits Chat" /></a></p> <p>Currently, Floobot’s code is based on our open-source <a href="https://github.com/Floobits/floobot">IRC Floobot</a>. We plan to split out the site parsing code and turn it into a node module. That way, everyone can take advantage of our tool. In the mean time, enjoy the useful descriptions of URLs. And if you have any suggestions or bug reports, please <a id="email_us" href="">email us</a> or <a href="https://github.com/Floobits/floobot/issues">open an issue</a>.</p> <p>Update: Our site parsing code is now in an open-source module. Feel free to use <a href="https://github.com/Floobits/node-fleece">Fleece</a> in your own projects.</p> <script type="text/javascript">document.getElementById("email_us").setAttribute("href","mail" + "to:" + "in" + "fo" + "@" + "floo" + "bits.com");</script> IntelliJ IDEA User Interface Updates 2015-03-24T16:25:54-07:00 https://news.floobits.com/2015/03/24/intellij-idea-ui-updates/ <p>We’ve listened to your feedback and made some big improvements to our IntelliJ IDEA plugin.</p> <p>First, it’s easier to see where others’ cursors are. When someone else moves to a new line, you’ll see a small indicator with their Gravatar and username. The border of the indicator matches their highlight color.</p> <p><img src="/images/intellij/balloon.png" width="500" alt="User indicator" /></p> <p>Second, we’ve updated the list of people connected to the workspace. Everyone now gets a larger version of their Gravatar, along with a list of the clients they are connected with. Previously, each client was a separate item in the list. Now, we’ve grouped them by user.</p> <p><img src="/images/intellij/user_list.png" width="500" alt="User list" /></p> <p>Also, right-clicking on a user opens a context menu allowing you to follow, unfollow, or kick clients or users. In addition, you can edit other users’ permissions from this menu.</p> <p><img src="/images/intellij/manage_users.png" width="500" alt="Manage users from the context menu." /></p> <p>One more thing: Our changes aren’t just cosmetic. We’ve improved performance! Specifically, highlights are rendered significantly faster.</p> <p>This update should work in the most recent version of IntelliJ IDEA and its forks: PHPStorm, RubyMine, PyCharm, WebStorm, Android Studio, CLion and AppCode.</p> <p>If you notice any issues, or have any feedback about these changes, don’t hesitate to contact us at support@floobits.com. We worked hard to make sure the changes performed well while being unobtrusive, but we’re open to feedback. Our IntelliJ IDEA plugin has steadily increased in usage, and we’re excited to continue improving it. Your suggestions are a big part of that.</p> On Moving to io.js 2015-02-23T15:09:46-08:00 https://news.floobits.com/2015/02/23/on-moving-to-io.js/ <p>Last weekend, we migrated our production environment from <a href="https://nodejs.org/">Node.js</a> to <a href="https://iojs.org/">io.js</a>.<sup><a href="#ref_1">[1]</a></sup> This move may seem premature, but we had several reasons for switching.</p> <p>For starters, we were growing increasingly frustrated with Node’s stagnation. Node.js v0.10 came out in March of 2013, 10 months after v0.8’s release. Work on v0.12 began the same month. In January of 2014, Joyent’s TJ Fontaine claimed <a href="https://www.joyent.com/blog/node-js-and-the-road-ahead">v0.12 was “imminent”</a>. Yet v0.12 finally shipped in February of 2015, over a year later. Compared to previous releases, those two years of development didn’t bring many changes. Just look at API differences between <a href="https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10">v0.8-v0.10</a> and <a href="https://github.com/joyent/node/wiki/Api-changes-between-v0.10-and-v0.12">v0.10-v0.12</a>. It’s sad that such a useful, popular project has run out of steam lately. Even Node’s latest release ships with old, unsupported versions of V8 (3.28.73) and libuv (1.0.2). io.js has current releases: V8 and libuv 1.4.0. These bring performance improvements, bug fixes, and many new features.</p> <p>The decision to switch wasn’t driven solely by Node’s issues. Many of io.js’s features appealed to us. For example, io.js supports much of <a href="https://iojs.org/en/es6.html">ECMAScript 6</a> by default, and its support is more extensive than Node’s <code>--harmony</code> flag. This may seem trivial, but it’s actually a big deal. ES6 transforms JavaScript into a more powerful, more forgiving, and generally more pleasant-to-use language. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings">Template strings</a> are a welcome bit of syntactic sugar. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let"><code>let</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const"><code>const</code></a> help programmers avoid whole classes of mistakes. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*">Generators</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a> make it easier to translate one’s mental models into straightforward code. In short, ES6 has changed how we write server-side JavaScript more than any major release of Node.js.</p> <p>Despite io.js’s rapid development, it has maintained or improved upon the stability of Node. io.js’s extensive test framework, frequent-but-small release cycle, and talented team of contributors have all helped to find and fix bugs faster than ever. io.js is the most stable server-side JavaScript framework today.</p> <p>Lastly, we found that the effort to switch from Node v0.12 to io.js was quite small. Since we were already planning on upgrading to Node v0.12, the benefits of io.js were worth the slight increase in work. Ours was a bit of a special case, as we had to do tweak a few of our <a href="https://iojs.org/api/addons.html">native modules</a>. For most users, io.js is a drop-in replacement.</p> <p>If you’re considering upgrading to Node v0.12, give io.js a try. You may be pleasantly surprised at the outcome.</p> <ol> <li><span id="ref_1"></span> If you missed our downtime page, you can always view it <a href="https://floobits.com/static/503.html">here</a>.</li> </ol> <hr /> <p><small> Thanks goes to <a href="irc://irc.freenode.net:6667/#io.js">#io.js on Freenode</a> for their suggestions and corrections.</small></p> <p>Please note that this post should not be construed as disparaging or insulting Node.js or its contributors. I personally wish them the best of luck and hope they can regain momentum. While we prefer io.js right now, that could easily change. After all, io.js was unheard of only 90 days ago. May the best product win. &lt;/small&gt;</p> New Feature: Follow Individual Users 2014-12-09T13:50:05-08:00 https://news.floobits.com/2014/12/09/Follow-Individual-Users/ <p>We have added a new feature! You can now follow changes from specific users in a workspace. Previously, it was only possible to follow everyone in a workspace. While useful, this can be distracting and hectic. If more than one person is editing at a time, you’re liable to get jumped back-and-forth between files. Some of our users requested a more fine-grained approach. Now we’ve added this to all of our editor plugins and our web-based editor.</p> <p>What follows are instructions for how use this feature in our various plugins. Note: You can only follow people who have edit permission in the workspace. In the case of editor plugins, people who do not have edit permissions do not appear in the list of users you can follow. You can follow more than one user at a time. Also, it’s still possible to follow all changes made in the workspace.</p> <h2 id="web-editor">Web Editor</h2> <p>To follow a specific user, click the magnet icon on their user image. To stop following that user, click it again. The magnet icon will reflect the current state. A solid looking magnet means you are following that user.</p> <h2 id="sublime-text">Sublime Text</h2> <p>To follow individual users in Sublime Text open up the command prompt and search for “Floobits - Follow User”, after selecting this option you will be presented with a list of usernames that you can follow. You should see the username of the person you are following in your status bar. To unfollow a user select “Floobits - Follow User” again and click the username you wish to unfollow. You can also stop following everyone with the “Floobits - Stop Following Workspace” command.</p> <p>Follow user in Sublime Text</p> <p><img src="/images/follow_user/st_follow_user.png" width="500" alt="Follow user" /></p> <p>Select user</p> <p><img src="/images/follow_user/st_select_user.png" width="500" alt="Select user" /></p> <p>Confirmation</p> <p><img src="/images/follow_user/st_follow_confirmation.png" width="500" alt="Confirmation" /></p> <p>Unfollow</p> <p><img src="/images/follow_user/st_unfollow.png" width="500" alt="Unfollow" /></p> <h2 id="intellij-webstorm-pycharm-rubymine-android-studio-phpstorm">IntelliJ, WebStorm, PyCharm, RubyMine, Android Studio, PHPStorm</h2> <p>In an IntelliJ based editor you can go to tools -&gt; Floobits -&gt; Follow Selected Users or search for <code>Follow Selected Users</code> from the command prompt (⌘/Cntrl+Shift+A). A list of users with edit permissions will appear. Check the box next to each username that you wish to follow. Open this same list whenever you want to unfollow anyone. To stop following completely select <code>Toggle Follow Mode</code> from the command prompt. You can also use the toolbar menu Tools -&gt; Floobits -&gt; “Disable follow mode” to achieve the same thing.</p> <p>Follow user in intellij</p> <p><img src="/images/follow_user/intellij_follow_user.png" width="500" alt="Follow user" /></p> <p>Select user</p> <p><img src="/images/follow_user/intellij_select_user.png" width="500" alt="select user" /></p> <h2 id="emacs">Emacs</h2> <p>In emacs type <code>M-x</code> and select <code>follow-user</code>. This will present a list of users with edit permissions you can follow. <code>floobits-follow-mode-toggle</code> will allow you to stop following all those you’ve elected to follow.</p> <p>Follow user in emacs</p> <p>&lt;img src=”/images/follow_user/emacs_follow_user.png” width=”250”/ alt=”Follow user in emacs”&gt;</p> <p>Select user</p> <p><img src="/images/follow_user/emacs_select_user.png" width="250" alt="Select user" /></p> <p>Confirmation</p> <p>&lt;img src=”/images/follow_user/emacs_follow_confirmation.png” width=”250” “Confirmation”/&gt;</p> <h2 id="neovim">Neovim</h2> <p>To follow specific individuals in NeoVim type <code>:FlooFollowUser</code>. To unfollow all followed users type <code>:FlooToggleFollowMode</code>.</p> <p>Follow user in Neovim</p> <p><img src="/images/follow_user/nvim_follow_user.png" width="500" /></p> <p>Select user</p> <p><img src="/images/follow_user/nvim_select_user.png" width="500" /></p> <p>That’s it! Enjoy!</p> Floobits Now Supports Neovim 2014-11-04T17:19:54-08:00 https://news.floobits.com/2014/11/04/floobits-now-supports-neovim/ <p>We’ve made a <a href="http://neovim.org/">Neovim</a>-compatible <a href="https://github.com/Floobits/floobits-neovim">plugin</a>. It deprecates our Vim plugin. We have updated all of our <a href="https://floobits.com/help/plugins/nvim">documentation</a> and references to Vim to reflect this. The reason for this change is simple: Neovim offers the asynchronous architecture required to make a real-time collaborative plugin painless.</p> <p>We have documented our efforts with Vim previously, <a href="https://news.floobits.com/2013/09/16/adding-realtime-collaboration-to-vim/">implementing collaboration</a> and a <a href="https://news.floobits.com/2013/09/17/adding-settimeout-to-vim/">setTimeout</a>. With Neovim, this was <a href="https://github.com/Floobits/floobits-neovim/blob/master/pythonx/nvim_floobits.py#L7">very simple to do</a>, requiring no hacks at all. Goodbye, <code>cursorhold</code> and <code>feedkeys</code>!</p> <p>Our Neovim plugin already offers a better experience than our Vim plugin. It has fewer bugs and performs better. Neovim is still considered alpha-quality, but we’ve found it works identically to Vim. Moving a <code>.vimrc</code> to <code>.nvimrc</code> and <code>.vim</code> to <code>.nvim</code> worked without any problems. Building Neovim on a Mac is painless.</p> <p>The biggest drawback is that <a href="https://github.com/tpope/vim-pathogen">Pathogen</a> is no longer supported because of a <a href="https://github.com/tpope/vim-pathogen/issues/144">bug handling special python directories</a>. Fortunately, <a href="https://github.com/gmarik/Vundle.vim">Vundle</a> works perfectly with Neovim, and makes it very easy to keep your plugins up to date.</p> <p>We’ll keep our Vim plugin supported as best as we can, but we want new users to avoid it. If you’re a Vim user, try out Neovim! It’s a much better experience.</p> Building an Office in Software 2014-07-22T15:21:12-07:00 https://news.floobits.com/2014/07/22/building-an-office-in-software/ <p>We use Floobits to develop Floobits, so we encounter many of the same pain points as our users. One problem we’ve had is chat. Often, we want to be able to communicate without necessarily working on the same code. Our first solution was to designate one workspace for chatting (both text and video). This worked, but the experience was definitely sub-optimal.</p> <p>In an office, it’s easy to tell when someone doesn’t want to be interrupted. When they’re not “in the zone”, you can walk up and ask for help or information. If you want to have a conversation without distracting others, there are meeting rooms. Staying in the same Google+ Hangout or keeping video chat open all day is a very different experience. Unlike in an office, a conversation between two people distracts everyone. We needed a way to talk to each other without forcing our coworkers to leave the video chat or mute their sound. So we built a new feature: Org chat.</p> <p><img src="/images/Screen Shot 2014-07-22 at 1.56.44 PM.png" style="max-width: 100%;" alt="A typical moment in Floobits chat" title="A typical moment in Floobits chat" /></p> <p>Every Floobits organization now has a chat page, which can only be accessed by members of that organization. People using org chat have a picture that updates periodically. Clicking on that picture starts a video chat with that person. You can rope in more people by clicking on more pictures. Clicking on a video stops the chat for that user. Clicking on yourself causes you to leave video chat.<sup><a href="#ref_1">[1]</a></sup> There’s also text chat for less attention-grabbing communication. This interaction is much closer to being in the same room. In some ways, it’s better. People aren’t distracted by others conversing nearby.</p> <p>While building this feature, we discovered it had other advantages. It uses much less bandwidth than continuous video chat. This makes it usable over metered (often mobile) connections. Also, it improves battery life for laptop users, since less time is spent powering the camera, encoding video, and transmitting it. All of these benefits have helped org chat quickly become the stardard way we interact when we’re not pairing.</p> <p>If you are a member of a Floobits org, you can try org chat with your teammates right now. Just go to <a href="https://floobits.com/dash/orgs">your org list</a> and click on a “Chat” button. The only requirements are a camera, microphone, and a WebRTC-capable browser such as Firefox or Chrome. Even Chrome on Android works.</p> <p>We like to release early and often, so consider the current org chat a first draft. We plan on adding some very useful features in the near future, including:</p> <ul> <li>Screen sharing.</li> <li>Indicators of who is currently video chatting.</li> <li>Showing who is in each workspace.</li> </ul> <p>We’re also toying with adding notes and an IRC bridge, but those features are farther off.</p> <p>It’s been almost a year since I wrote, “<a href="http://geoff.greer.fm/2013/08/28/an-office-made-of-software/">…any disadvantage of remote working is a software problem.</a>” I still think that’s true. Remote collaboration software still has a long way to go, but we’re doing our best to move it forward.</p> <ol> <li><span id="ref_1"></span>Credit where credit is due. This workflow was inspired by <a href="https://www.sqwiggle.com/">Sqwiggle</a>.</li> </ol> Dear Network Admins, Stop Blocking Non-HTTP/HTTPS 2014-07-17T15:05:14-07:00 https://news.floobits.com/2014/07/17/dear-network-admins-stop-blocking-non-http/ <p>One of the biggest problems we’ve had at Floobits is network filtering. Many network administrators block outbound connections that use protocols besides HTTP/HTTPS. It’s not uncommon for this to happen in schools, large companies, government offices, and hotels. This is troublesome.</p> <p>We recently released new versions of our plugins that detect port blocking and work-around it. If our plugins can’t connect to <code>floobits.com</code> on port <code>3448</code>, they try <code>proxy.floobits.com</code> on port <code>443</code>. All of our plugins communicate over TLS, so the network traffic looks like HTTPS. Unless the connection is man-in-the-middled, it should work without users noticing. Building this took weeks of planning, development, and testing. This was time that should have been used for more productive purposes.</p> <p>Network admins make various excuses for this censorship. It prevents abuse. It stops people from using protocols associated with piracy. These points are valid, but there are better ways to address them. More importantly, the costs far outweigh the benefits. Unfortunately, these costs are paid by users and developers, not network admins.</p> <p>To give a concrete example: I recently vacationed in Canada with my parents and siblings. We stayed at one hotel that restricted outbound network access. This was particularly frustrating at the time, because my grandmother had passed away recently, and my mother was trying to get grandma’s headstone made. Because outbound SMTP was blocked, she couldn’t change a message on the headstone. She fortunately managed to find a different connection, but the point is made: one cannot fully anticipate the consequences of restricting outbound Internet access. Many users hardly notice the restrictions. Many are annoyed by them. And occasionally, someone’s quality of life is seriously affected by port blocking.</p> <p>The Internet is more than just the web. There are thousands of protocols besides HTTP. Individually, each one may not be popular, but the majority of people use some of them. Blocking these protocols harms everyone. It frustrates users. It forces developers to build work-arounds. It stifles innovation in network protocols. Competent administrators can secure their networks and prevent abuse without resorting to such heavy-handed tactics. If you are a network administrator, I urge you to reconsider port blocking.</p> Error Reporting in Floobits 2014-07-02T13:50:05-07:00 https://news.floobits.com/2014/07/02/error-reporting-in-floobits/ <p>Automated error reporting is an oft-overlooked way to improve software. Linters, tests, QA, and pairing can all reduce bugs, but they don’t answer the most important question: <strong>Do users encounter errors?</strong> Internal testing simply can’t imagine (let alone discover) the myriad ways in which users will break your code. Detecting and reporting errors gives feedback that can find gaps in your testing and QA. Error reporting also makes customers happier. Users love it when we contact them, apologize for an error they ran into, and mention that we’ve shipped a fix.</p> <p>We make use of error reporting throughout our software. We receive reports from our editor plugins, client-side JavaScript, and server-side code. If anything breaks, we know.</p> <p>The specifics of each form of error reporting are explained below, in the hope that others may learn from our set-up.</p> <h2 id="django">Django</h2> <p>We use a customized version of <a href="https://docs.djangoproject.com/en/1.7/howto/error-reporting/">Django’s exception reporting</a>. Instead of sending e-mails from Python, errors are added to an on-disk queue, which our <a href="https://github.com/Floobits/gurgitator">Gurgitator</a> service consumes. This lets <a href="https://floobits.com/static/500.html">our error page</a> load sooner, since the HTTP response isn’t blocked by sending an e-mail.</p> <h2 id="back-end-services">Back-end services</h2> <p>Our back-end services use similar error reporting. All of our services are managed by <a href="http://smarden.org/runit/">runit</a>. If a service dies with a non-zero exit code, the service’s <code>finish</code> script drops a message into Gurgitator’s job directory.</p> <h2 id="javascript">JavaScript</h2> <p>We use browser <a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers.onerror">onerror</a> handlers to detect JavaScript errors. We then log those errors by hitting our own API endpoint.</p> <p><code>onerror</code> can generate a lot of errors in quick succession — thousands in some cases — so we uniqify and throttle errors. Throttled occurrences are aggregated before being reported. We also filter out errors caused by extensions, content scripts and unsupported browsers.</p> <p>Chrome sends additional helpful information such as a stack trace and the line and column number which helps since our JavaScript is minified to a single line.</p> <p>With each report, we attempt to capture information about the state of the application while being sure to remove any sensitive information. We also use our client-side logging API for other events, such as timeouts that might occur when users attempt to link their editor plugin with their Floobits account on the website.</p> <h2 id="editor-plugins">Editor Plugins</h2> <p>Each of our editor plugins makes use of the same client reporting API that the website uses. We use it to notify us when our plugins crash. Detecting plugin crashes can be challenging as there is no all-encompassing error event we can handle. We have to make use of <code>try except</code> in Python and <code>try catch</code> in Java to gain as wide as coverage as possible. Asynchronous calls will not be caught by a single try catch so there are multiple points where we need to handle potential exceptions.</p> <p>Though some errors may not end up having been reported to us, it is likely something was logged either via the editor’s default logging system or to a shared floobits log file. If users are running into problems it always helps if they send us a log file as well as provide information about the editor they are using, its version and the operating system version, as well as the version of the floobits plugin.</p> <p>Given that we are always making improvements to our plugins and fixing problems when we see them, it is good if everyone keeps their Floobits plugin up to date. The best way to do this is to install the plugin via the recommended method as documented in our help page for each supported editor. For example, Sublime Text users using Package Control and IntelliJ IDEA users using the Jetbrains plugin repository should receive automatic plugin updates or notifications. If the plugin was installed manually, users must remember to update regularly or at least when they’ve encountered an issue.</p>