Reduce bouncing on the cache lock in ssl_update_cache. ssl_update_cache takes the cache lock to add to the session cache, releases it, and then immediately takes and releases the lock to increment handshakes_since_cache_flush. Then, in 1/255 connections, does the same thing again to flush stale sessions. Merge the first two into one lock. In doing so, move ssl_update_cache to ssl_session.cc, so it can access a newly-extracted add_session_lock. Also remove the mode parameter (the SSL knows if it's a client or server), and move the established_session != session check to the caller, which more directly knows whether there was a new session. Also add some TSan coverage for this path in the tests. In an earlier iteration of this patch, I managed to introduce a double-locking bug because we weren't testing it at all. Confirmed this test catches both double-locking and insufficient locking. (It doesn't seem able to catch using a read lock instead of a write lock in SSL_CTX_flush_sessions, however. I suspect the hash table is distributing the cells each thread touches.) Update-Note: This reshuffles some locks around the session cache. (Hopefully for the better.) Change-Id: I78dca53fda74e036b90110cca7fbcc306a5c8ebe Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48133 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index d6edcfe..4bb9c32 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -5427,7 +5427,8 @@ } } - // Hit the maximum session cache size across multiple threads + // Hit the maximum session cache size across multiple threads, to test the + // size enforcement logic. size_t limit = SSL_CTX_sess_number(server_ctx_.get()) + 2; SSL_CTX_sess_set_cache_size(server_ctx_.get(), limit); { @@ -5443,6 +5444,59 @@ } EXPECT_EQ(SSL_CTX_sess_number(server_ctx_.get()), limit); } + + // Reset the session cache, this time with a mock clock. + ASSERT_NO_FATAL_FAILURE(ResetContexts()); + SSL_CTX_set_options(server_ctx_.get(), SSL_OP_NO_TICKET); + SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH); + SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH); + SSL_CTX_set_current_time_cb(server_ctx_.get(), CurrentTimeCallback); + + // Make some sessions at an arbitrary start time. Then expire them. + g_current_time.tv_sec = 1000; + bssl::UniquePtr<SSL_SESSION> expired_session1 = + CreateClientSession(client_ctx_.get(), server_ctx_.get()); + ASSERT_TRUE(expired_session1); + bssl::UniquePtr<SSL_SESSION> expired_session2 = + CreateClientSession(client_ctx_.get(), server_ctx_.get()); + ASSERT_TRUE(expired_session2); + g_current_time.tv_sec += 100 * SSL_DEFAULT_SESSION_TIMEOUT; + + session1 = CreateClientSession(client_ctx_.get(), server_ctx_.get()); + ASSERT_TRUE(session1); + + // Every 256 connections, we flush stale sessions from the session cache. Test + // this logic is correctly synchronized with other connection attempts. + static const int kNumConnections = 256; + { + std::vector<std::thread> threads; + threads.emplace_back([&] { + for (int i = 0; i < kNumConnections; i++) { + connect_with_session(nullptr); + } + }); + threads.emplace_back([&] { + for (int i = 0; i < kNumConnections; i++) { + connect_with_session(nullptr); + } + }); + threads.emplace_back([&] { + // Never connect with |expired_session2|. The session cache eagerly + // removes expired sessions when it sees them. Leaving |expired_session2| + // untouched ensures it is instead cleared by periodic flushing. + for (int i = 0; i < kNumConnections; i++) { + connect_with_session(expired_session1.get()); + } + }); + threads.emplace_back([&] { + for (int i = 0; i < kNumConnections; i++) { + connect_with_session(session1.get()); + } + }); + for (auto &thread : threads) { + thread.join(); + } + } } TEST_P(SSLVersionTest, SessionTicketThreads) {