Duplication Bug Explained

The "decaffeinate" converter will add returns to javascript to be sure it matches the default coffeescript semantics. Many are unnecessary but when we removed one too many we saw pages items on some pages duplicated.

Unexpected Duplication

The symptom we saw was pages with the yellow halo had each item rendered twice with each copy adjacent before rendering the next item. How does this even happen?

The items in a page are rendered one after another in a for-loop that will await for a rendering plugin to be loaded.

Imagine a loop similar to the following where each item is added to the DOM as rendered.

async function renderPage(page} { for (item in page.story) { await renderItem(item) } }

When this async function was inadvertently called twice for the same page the two for-loops ran in lock-step causing each item to be rendered twice before advancing to the next item.

When we construct a lineup url we alternate sites and slugs except when the site is the origin we say "view" rather than repeat the origin's domain name. There are other options.

- keyword "local" -- show page from local storage - keyword "origin" -- show page fetched from the server - keyword "recycler" -- show discarded page - keyword "view" -- choose local storage, if available

Three of these are supported by standard site adapters where the last is a wrapper that chooses local or origin.

const adapter = { 'local': wiki.local, 'origin': wiki.origin, 'recycler': wiki.recycler, 'view': localBeforeOrigin }

The localBeforeOrigin adapter was coded to return as an early exit when possible, otherwise it fell into the code that handled all other cases. We meant to render one version or the other but not both.

We saw that no return value was expected so too quickly removed the return statement which had us launching two parallel rendering. Oops. Structured programming suggests this should have been coded as an if-then-else with a single return for a less error prone control flow. wikipedia

This was not an easy mistake to find. Single stepping through the logic got us to the right place but we couldn't see any duplications in the arguments.

Eventually with a breakpoint on renderPage I would count how many times it was hit when rendering a local storage page and that was twice.

Stack trace from two breakpoints compared.