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 collection modified exception which causes rare HTTP 500s #5830

Merged
merged 1 commit into from
Apr 20, 2018

Conversation

joelverhagen
Copy link
Member

Had some free time so I wanted to fix some intermittent 500s related to getting download counts on an ID. This PR addresses #5779.

Things done:

  1. Use ConcurrentDictionary in CloudDownloadCountService instead of Dictionary. This allows concurrent reads (via HTTP requests) and writes (via a background refresh every 15 minutes)
  2. Don't lowercase. Using StringComparer.OrdinalIgnoreCase. This should reduce allocations (not measured though).
  3. Add unit tests, in particular one that repro'd the race condition on Dictionary.

Performance analysis:

Version Operation Duration
Dictionary Refresh 17.13 seconds
Dictionary TryGetDownloadCountForPackageRegistration 0.0009 ms
ConcurrentDictionary Refresh 17.70 seconds
ConcurrentDictionary TryGetDownloadCountForPackageRegistration 0.0011 ms

For Refresh, I copied the downloads.v1.json from PROD to my own storage account and ran gallery code from my dev box. 100 iterations. Every 10 iterations I would refresh from scratch.

For TryGetDownloadCountForPackageRegistration, I ran all PROD IDs through TryGetDownloadCountForPackageRegistration, which 176,157 invocations of the method. 245 iterations. Every 49 iterations I re-initialized the server (new Refresh).

I thing the stability of ConcurrentDictionary is worth the minor performance decrease. We can consider an alternative approach if you guys think its worth it.


while (jsonReader.Read())
using (var jsonReader = new JsonTextReader(new StreamReader(blobStream)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make sure all the IDisposables will work well with each other (nothing missing, no double dispose that throws..)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified.

There are three IDisposable instances in question here:

  1. The stream of the blob contents, returned by CloudBlob.OpenRead(). This is wrapped in a using. This is also passed to StreamReader constructor.
  2. StreamReader. This is passed to JsonTextReader constructor.
  3. JsonTextReader. This is wrapped in a using.

IDisposable number 1 and 3 are in using blocks and number 2 is owned by JsonTextReader which, by default, disposes the provided TextReader.

In short, we're good.

Copy link
Contributor

@skofman1 skofman1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Add unit tests to verify casing changes and repro of bug
Use ConcurrentDictionary instead of Dictionary
Address #5779
@joelverhagen joelverhagen merged commit f120d52 into dev Apr 20, 2018
@joelverhagen joelverhagen deleted the jver-5779 branch April 20, 2018 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants