Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race condition when sending daily stats (take 2) #29

Merged
merged 5 commits into from Jun 3, 2019
Merged

Conversation

@rafeca
Copy link
Contributor

@rafeca rafeca commented Jun 3, 2019

Summary

This PR adds a simple locking system using the localStorage mechanism to prevent two different instances of telemetry (which could run in parallel from different windows/tabs in a browser/electron environment).

This, combined with #28, should solve most concurrency issues on the telemetry package.

Remaining issue

There's still one remaining potential issue which is minor (at least compared to this one and the one fixed by #28):

Any event happening while the reporting is being done is lost: Since happens because we're clearing all the events once the current events are being reported.

In order to fix this, we have two options:

  1. Implement a queueing system which places the events that happen while previous events are being sent in a temporary structure and then stores them in the database once the events got sent successfully.
  2. Instead of deleting all events from the database once they are sent, only delete the ones that were actually sent (this could potentially be slow or harder to implement).

Anyways, we should fix this remaining issue as a separate PR since it's orthogonal to this work.

rafeca added 4 commits Jun 3, 2019
Debugging current tests was a mess since previously created stores kept
running and reporting their states. With this commit, and `end()` method
has been added to the Store class that allows to stop the interval that
reports events.

This method can also be used by consumers whenever the store needs to be
disposed.
…tats

This commits adds a simple locking mechanism to prevent the race
conditions that could happen if two different telemetry instances
run in parallel.
@rafeca rafeca requested a review from as-cii Jun 3, 2019
@as-cii
as-cii approved these changes Jun 3, 2019
Copy link

@as-cii as-cii left a comment

I left one comment to clarify one aspect of the newly-introduced code, but this looks good otherwise!

@@ -160,10 +170,26 @@ export class StatsStore {
if (this.optOut || this.isDevMode) {
return;
}
const stats = await this.getDailyStats();

// If multiple instances of `telemetry` are being run from different

This comment has been minimized.

@as-cii

as-cii Jun 3, 2019

When we discussed this synchronously, I believe you mentioned localStorage would itself implement a mutex around accesses to any of its keys, and that the lock would be released before the next tick of the loop. Are we taking advantage of those semantics here? If so, can we document that as well (maybe linking to some resource describing that behavior)?

This comment has been minimized.

@rafeca

rafeca Jun 3, 2019
Author Contributor

Yes exactly, I've just double-checked more deeply an this seems to be quite complex thing 😅

In the W3C Storage standard there's some information about it.

But then there's this article (quite old imho), which states that localStorage is not thread safe in most browsers.

After researching a bit, seems like at the time (2012) most of the engines didn't implement the mutex (to be fair, most of the browsers didn't run tabs in separate threads), so this could be an issue back then. It even seems that Chrome removed temporarily the mutexes for performance reasons. Today, with the predominance of chromium and with the fact that Atom runs on electron this doesn't seem to be a problem.

Nowadays, chromium (and therefore electron) seems to have a few mechanism to avoid the race conditions but honestly I haven't gone deep into their code to fully understand what's doing.

To be completely sure, I've just tried by brute force to cause a race condition both on Atom and Chrome and was unable to do so. This is the code that I've used:

// window 1
const timer = setInterval(() => {
  if (localStorage.getItem('foo') === '1') {
    return;
  }  
  console.log('trying to cause a race condition');

  let i = 4000000;
  while (i--) {
    if (localStorage.getItem('foo') === '1') {
      console.error('Race condition!');
      clearInterval(timer);
      break;
    }
  }
  console.log('no race condition yet...');
}, 1000);

// =========================
// window 2
let value = '1';
setInterval(() => {
  value = value === '1' ? '0' : '1';
  localStorage.setItem('foo', value);
}, 1);

I've run this super abusive code for almost an hour with no race condition happening, so I'm pretty sure that even if there was a possibility of a race condition it won't ever happen in the metrics scenario.

I'm gonna add a comment linking to the standard though 😄

@rafeca rafeca merged commit 83bab62 into master Jun 3, 2019
2 checks passed
2 checks passed
Travis CI - Branch Build Passed
Details
Travis CI - Pull Request Build Passed
Details
@rafeca rafeca deleted the fix-race-condition branch Jun 3, 2019
@rafeca rafeca self-assigned this Jun 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

2 participants
You can’t perform that action at this time.