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 in memory storage #2669

Closed
wants to merge 1 commit into from

Conversation

Hrily
Copy link

@Hrily Hrily commented Oct 8, 2023

Description

This commit updates Conn method of memory storage to fix race conditions.

The Conn method returned the reference to underlying map which could be used
for read/write and had a risk of race condition.

This commit fixes it by updating the method to take in a function to access
underlying map, which is wrapped in lock to make it thread safe.

Fixes: #2400

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • For new functionalities I follow the inspiration of the express js framework and built them similar in usage
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation - /docs/ directory for https://docs.gofiber.io/
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • If new dependencies exist, I have checked that they are really necessary and agreed with the maintainers/community (we want to have as few dependencies as possible)
  • I tried to make my code as fast as possible with as few allocations as possible
  • For new code I have written benchmarks so that they can be analyzed and improved

Commit formatting:

Use emojis on commit messages so it provides an easy way of identifying the purpose or intention of a commit. Check out the emoji cheatsheet here: https://gitmoji.carloscuesta.me/

This commit updates `Conn` method of memory storage to fix race conditions.

Fixes: gofiber#2400
@welcome
Copy link

welcome bot commented Oct 8, 2023

Thanks for opening this pull request! 🎉 Please check out our contributing guidelines. If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

defer s.mux.RUnlock()
return s.db
// ConnLocked can be used to access the underlying db in a thread-safe manner.
func (s *Storage) ConnLocked(f func(map[string]entry)) {
Copy link
Member

Choose a reason for hiding this comment

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

This is an interface, we shouldn't rename it to avoid breaking changes.

Copy link
Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

https://github.com/gofiber/storage/blob/main/storage.go

We may need to discuss this, since it's implemented differently in multiple places 🤔

Copy link
Author

Choose a reason for hiding this comment

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

As far as I understand, this method is only provided by memory.Storage, because I don't think any other storage will return a Conn of type map[string]entry.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

I see

I think we can make Storage.db a sync.Map so we can return it as it is from Conn(), without worrying about thread safety.

Thoughts @gaby , @ReneWerner87 ?

Copy link
Author

Choose a reason for hiding this comment

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

A ping on this in case it was missed. @gaby @ReneWerner87

Copy link
Member

Choose a reason for hiding this comment

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

sync.Map is ok, but slower or ?

Copy link
Author

Choose a reason for hiding this comment

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

I can run the Benchmark_Storage_Memory and see if it's better or worse.

Copy link
Author

Choose a reason for hiding this comment

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

@ReneWerner87 sync.Map based implementation is slower than existing implementation for Benchmark_Storage_Memory:

Existing Implementation Results:

Screenshot 2023-10-16 at 9 20 21 PM

sync.Map based implementation Results:

Screenshot 2023-10-16 at 9 20 36 PM

But our choice should not depend on the benchmark we have. From sync.Map Documentation:

The Map type is optimized for two common use cases: (1) when the entry for a given key is only ever written once but read many times, as in caches that only grow, or (2) when multiple goroutines read, write, and overwrite entries for disjoint sets of keys. In these two cases, use of a Map may significantly reduce lock contention compared to a Go map paired with a separate Mutex or RWMutex.

So if Storage's use case aligns with one of the above two points, we can go ahead and use sync.Map. Else we can have another interface layer on top of vanilla map with RWMutex which we can return from Conn()

@ReneWerner87
Copy link
Member

i just thought about the bug (#2360) again
can you please first create a test case which produces the error

with the sync.map I don't know yet, is already a considerable time loss for the driver which should normally be fastest

@ReneWerner87
Copy link
Member

maybe it would also be ok if we modify the memory storage driver, as long as we don't modify the internal memory driver which is the default one

@Hrily
Copy link
Author

Hrily commented Oct 18, 2023

can you please first create a test case which produces the error

@ReneWerner87 Test_Storage_Memory_ConnLocked_Race tests for this. It was failing for current Conn() implementation, i.e. with race error of reading while writing to map.

@ReneWerner87
Copy link
Member

mutext handling should take place outside or with an adjustment of the storage interface in the storage project

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🚀 [Feature]: Sustainably prevent data races
3 participants