Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,27 @@ class SimpleThreadSafeCache {

auto getMapIterator(const KeyT &key, CacheGeneratorFunction<ValueT> auto generator) const
{
std::lock_guard<std::mutex> lock(mutex_);
{
std::lock_guard<std::mutex> lock(mutex_);
if (auto it = map_.find(key); it != map_.end()) {
// Move accessed item to front of list
list_.splice(list_.begin(), list_, it->second);
return it;
}
}

// The generator runs without the lock: it may block on another thread
// that needs this cache. On iOS, text measurement creates an
// NSTextStorage whose editing notification can wait for the main queue,
// while the main thread waits for this lock to measure text.
auto value = generator();

std::lock_guard<std::mutex> lock(mutex_);
if (auto it = map_.find(key); it != map_.end()) {
// Move accessed item to front of list
// Another thread generated the same key meanwhile
list_.splice(list_.begin(), list_, it->second);
return it;
}

auto value = generator();
// Add new value to front of list and map
list_.emplace_front(key, value);
auto [it, _] = map_.insert_or_assign(key, list_.begin());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
#include <gtest/gtest.h>
#include <react/utils/SimpleThreadSafeCache.h>

#include <chrono>
#include <future>
#include <thread>

namespace facebook::react {

TEST(EvictingCacheMapTest, BasicInsertAndGet) {
Expand Down Expand Up @@ -37,4 +41,40 @@ TEST(EvictingCacheMapTest, Eviction) {
EXPECT_EQ(cache.get(3), "three");
}

TEST(EvictingCacheMapTest, GeneratorDoesNotBlockOtherThreads) {
SimpleThreadSafeCache<int, std::string, 3> cache;
std::promise<void> otherThreadDone;
std::thread other;

// The generator waits for a second thread that reads the same cache, as an
// iOS text measurement waits for the main thread.
auto value = cache.get(1, [&]() {
other = std::thread([&]() {
cache.get(2, []() { return std::string("two"); });
otherThreadDone.set_value();
});
auto status =
otherThreadDone.get_future().wait_for(std::chrono::seconds(5));
return std::string(
status == std::future_status::ready ? "one" : "blocked");
});
other.join();

EXPECT_EQ(value, "one");
EXPECT_EQ(cache.get(2), "two");
}

TEST(EvictingCacheMapTest, ConcurrentMissKeepsFirstInsertedValue) {
SimpleThreadSafeCache<int, std::string, 3> cache;

auto value = cache.get(1, [&]() {
std::thread other([&]() { cache.get(1, []() { return std::string("first"); }); });
other.join();
return std::string("second");
});

EXPECT_EQ(value, "first");
EXPECT_EQ(cache.get(1), "first");
}

} // namespace facebook::react
Loading