Add RTC_DCHECK that port_ is always valid in Connection.

This patch is a follow up to https://webrtc-review.googlesource.com/c/src/+/260943
which made it possible to destroy a Port before a Connection (on that
port).

This patch "reverses" this, by adding RTC_DHECK, and fixes the Port
destructor to release the Connections before invalidating the WeakPtr<>.

Currently there are no known occurrences where a Connection is destroyed after it's Port is. But prior to the change in Port destructor, a bunch unit tests failed on the newly added DCHECKs.

In addition:
a) modify StunReqquestManager to remove entry from hash before calling callback. This makes it possible for callback to modify (clear) hash.
b) clear pending requests when disconnecting from port, "should not be needed", depends on a)
c) add a getter for pending_delete()

Bug: webrtc:13892, webrtc:13865
Change-Id: I5d18f2db8d93b7cc25d18bd620063589ee9257c9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322861
Reviewed-by: Per Kjellander <perkj@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40907}
This commit is contained in:
Jonas Oreland 2023-10-11 10:02:24 +02:00 committed by WebRTC LUCI CQ
parent ddd6b69572
commit c4f3919fdd
5 changed files with 77 additions and 13 deletions

View file

@ -262,14 +262,17 @@ const Candidate& Connection::remote_candidate() const {
} }
const rtc::Network* Connection::network() const { const rtc::Network* Connection::network() const {
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in network()";
return port()->Network(); return port()->Network();
} }
int Connection::generation() const { int Connection::generation() const {
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in generation()";
return port()->generation(); return port()->generation();
} }
uint64_t Connection::priority() const { uint64_t Connection::priority() const {
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in priority()";
if (!port_) if (!port_)
return 0; return 0;
@ -806,13 +809,14 @@ void Connection::Prune() {
void Connection::Destroy() { void Connection::Destroy() {
RTC_DCHECK_RUN_ON(network_thread_); RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(port_) << "Calling Destroy() twice?"; RTC_DCHECK(port_) << ToDebugId() << ": port_ null in Destroy()";
if (port_) if (port_)
port_->DestroyConnection(this); port_->DestroyConnection(this);
} }
bool Connection::Shutdown() { bool Connection::Shutdown() {
RTC_DCHECK_RUN_ON(network_thread_); RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(port_) << ToDebugId() << ": Calling Shutdown() twice?";
if (!port_) if (!port_)
return false; // already shut down. return false; // already shut down.
@ -832,6 +836,9 @@ bool Connection::Shutdown() {
// information required for logging needs access to `port_`. // information required for logging needs access to `port_`.
port_.reset(); port_.reset();
// Clear any pending requests (or responses).
requests_.Clear();
return true; return true;
} }
@ -846,6 +853,7 @@ void Connection::FailAndPrune() {
// will be nulled. // will be nulled.
// In such a case, there's a chance that the Port object gets // In such a case, there's a chance that the Port object gets
// deleted before the Connection object ends up being deleted. // deleted before the Connection object ends up being deleted.
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in FailAndPrune()";
if (!port_) if (!port_)
return; return;
@ -882,6 +890,7 @@ void Connection::set_selected(bool selected) {
void Connection::UpdateState(int64_t now) { void Connection::UpdateState(int64_t now) {
RTC_DCHECK_RUN_ON(network_thread_); RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in UpdateState()";
if (!port_) if (!port_)
return; return;
@ -958,6 +967,7 @@ int64_t Connection::last_ping_sent() const {
void Connection::Ping(int64_t now, void Connection::Ping(int64_t now,
std::unique_ptr<StunByteStringAttribute> delta) { std::unique_ptr<StunByteStringAttribute> delta) {
RTC_DCHECK_RUN_ON(network_thread_); RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in Ping()";
if (!port_) if (!port_)
return; return;
@ -1251,11 +1261,13 @@ std::string Connection::ToDebugId() const {
uint32_t Connection::ComputeNetworkCost() const { uint32_t Connection::ComputeNetworkCost() const {
// TODO(honghaiz): Will add rtt as part of the network cost. // TODO(honghaiz): Will add rtt as part of the network cost.
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in ComputeNetworkCost()";
return port()->network_cost() + remote_candidate_.network_cost(); return port()->network_cost() + remote_candidate_.network_cost();
} }
std::string Connection::ToString() const { std::string Connection::ToString() const {
RTC_DCHECK_RUN_ON(network_thread_); RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in ToString()";
constexpr absl::string_view CONNECT_STATE_ABBREV[2] = { constexpr absl::string_view CONNECT_STATE_ABBREV[2] = {
"-", // not connected (false) "-", // not connected (false)
"C", // connected (true) "C", // connected (true)
@ -1457,6 +1469,8 @@ void Connection::OnConnectionRequestResponse(StunRequest* request,
void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request, void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
StunMessage* response) { StunMessage* response) {
RTC_DCHECK(port_) << ToDebugId()
<< ": port_ null in OnConnectionRequestErrorResponse";
if (!port_) if (!port_)
return; return;
@ -1610,6 +1624,8 @@ ConnectionInfo Connection::stats() {
void Connection::MaybeUpdateLocalCandidate(StunRequest* request, void Connection::MaybeUpdateLocalCandidate(StunRequest* request,
StunMessage* response) { StunMessage* response) {
RTC_DCHECK(port_) << ToDebugId()
<< ": port_ null in MaybeUpdateLocalCandidate";
if (!port_) if (!port_)
return; return;
@ -1754,6 +1770,7 @@ ProxyConnection::ProxyConnection(rtc::WeakPtr<Port> port,
int ProxyConnection::Send(const void* data, int ProxyConnection::Send(const void* data,
size_t size, size_t size,
const rtc::PacketOptions& options) { const rtc::PacketOptions& options) {
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in Send()";
if (!port_) if (!port_)
return SOCKET_ERROR; return SOCKET_ERROR;

View file

@ -111,6 +111,7 @@ class RTC_EXPORT Connection : public CandidatePairInterface {
bool connected() const; bool connected() const;
bool weak() const; bool weak() const;
bool active() const; bool active() const;
bool pending_delete() const { return !port_; }
// A connection is dead if it can be safely deleted. // A connection is dead if it can be safely deleted.
bool dead(int64_t now) const; bool dead(int64_t now) const;

View file

@ -188,8 +188,8 @@ void Port::Construct() {
Port::~Port() { Port::~Port() {
RTC_DCHECK_RUN_ON(thread_); RTC_DCHECK_RUN_ON(thread_);
CancelPendingTasks();
DestroyAllConnections(); DestroyAllConnections();
CancelPendingTasks();
} }
const absl::string_view Port::Type() const { const absl::string_view Port::Type() const {

View file

@ -133,31 +133,39 @@ bool StunRequestManager::CheckResponse(StunMessage* msg) {
} }
} }
bool success = true;
if (!msg->GetNonComprehendedAttributes().empty()) { if (!msg->GetNonComprehendedAttributes().empty()) {
// If a response contains unknown comprehension-required attributes, it's // If a response contains unknown comprehension-required attributes, it's
// simply discarded and the transaction is considered failed. See RFC5389 // simply discarded and the transaction is considered failed. See RFC5389
// sections 7.3.3 and 7.3.4. // sections 7.3.3 and 7.3.4.
RTC_LOG(LS_ERROR) << ": Discarding response due to unknown " RTC_LOG(LS_ERROR) << ": Discarding response due to unknown "
"comprehension-required attribute."; "comprehension-required attribute.";
success = false; requests_.erase(iter);
return false;
} else if (msg->type() == GetStunSuccessResponseType(request->type())) { } else if (msg->type() == GetStunSuccessResponseType(request->type())) {
if (!msg->IntegrityOk() && !skip_integrity_checking) { if (!msg->IntegrityOk() && !skip_integrity_checking) {
return false; return false;
} }
request->OnResponse(msg); // Erase element from hash before calling callback. This ensures
// that the callback can modify the StunRequestManager any way it
// sees fit.
std::unique_ptr<StunRequest> owned_request = std::move(iter->second);
requests_.erase(iter);
owned_request->OnResponse(msg);
return true;
} else if (msg->type() == GetStunErrorResponseType(request->type())) { } else if (msg->type() == GetStunErrorResponseType(request->type())) {
request->OnErrorResponse(msg); // Erase element from hash before calling callback. This ensures
// that the callback can modify the StunRequestManager any way it
// sees fit.
std::unique_ptr<StunRequest> owned_request = std::move(iter->second);
requests_.erase(iter);
owned_request->OnErrorResponse(msg);
return true;
} else { } else {
RTC_LOG(LS_ERROR) << "Received response with wrong type: " << msg->type() RTC_LOG(LS_ERROR) << "Received response with wrong type: " << msg->type()
<< " (expecting " << " (expecting "
<< GetStunSuccessResponseType(request->type()) << ")"; << GetStunSuccessResponseType(request->type()) << ")";
return false; return false;
} }
requests_.erase(iter);
return success;
} }
bool StunRequestManager::empty() const { bool StunRequestManager::empty() const {

View file

@ -54,15 +54,15 @@ class StunRequestTest : public ::testing::Test {
request_count_++; request_count_++;
} }
void OnResponse(StunMessage* res) { virtual void OnResponse(StunMessage* res) {
response_ = res; response_ = res;
success_ = true; success_ = true;
} }
void OnErrorResponse(StunMessage* res) { virtual void OnErrorResponse(StunMessage* res) {
response_ = res; response_ = res;
failure_ = true; failure_ = true;
} }
void OnTimeout() { timeout_ = true; } virtual void OnTimeout() { timeout_ = true; }
protected: protected:
rtc::AutoThread main_thread_; rtc::AutoThread main_thread_;
@ -216,4 +216,42 @@ TEST_F(StunRequestTest, TestUnrecognizedComprehensionRequiredAttribute) {
EXPECT_FALSE(timeout_); EXPECT_FALSE(timeout_);
} }
class StunRequestReentranceTest : public StunRequestTest {
public:
void OnResponse(StunMessage* res) override {
manager_.Clear();
StunRequestTest::OnResponse(res);
}
void OnErrorResponse(StunMessage* res) override {
manager_.Clear();
StunRequestTest::OnErrorResponse(res);
}
};
TEST_F(StunRequestReentranceTest, TestSuccess) {
auto* request = new StunRequestThunker(manager_, this);
std::unique_ptr<StunMessage> res =
request->CreateResponseMessage(STUN_BINDING_RESPONSE);
manager_.Send(request);
EXPECT_TRUE(manager_.CheckResponse(res.get()));
EXPECT_TRUE(response_ == res.get());
EXPECT_TRUE(success_);
EXPECT_FALSE(failure_);
EXPECT_FALSE(timeout_);
}
TEST_F(StunRequestReentranceTest, TestError) {
auto* request = new StunRequestThunker(manager_, this);
std::unique_ptr<StunMessage> res =
request->CreateResponseMessage(STUN_BINDING_ERROR_RESPONSE);
manager_.Send(request);
EXPECT_TRUE(manager_.CheckResponse(res.get()));
EXPECT_TRUE(response_ == res.get());
EXPECT_FALSE(success_);
EXPECT_TRUE(failure_);
EXPECT_FALSE(timeout_);
}
} // namespace cricket } // namespace cricket