Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(125)

Unified Diff: net/quic/chromium/quic_chromium_client_session.cc

Issue 2319343004: Makes migration on write error asynchronous to avoid reentrancy issues (Closed)
Patch Set: added tests for async write before notification and fixed call to OnCanWrite Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/quic/chromium/quic_chromium_client_session.cc
diff --git a/net/quic/chromium/quic_chromium_client_session.cc b/net/quic/chromium/quic_chromium_client_session.cc
index 258b0c6b4eac7cfb2498a95f5b29fc00dbc2a388..11019aa75af5c583e7e0a532c8d5c17f54b5c4ca 100644
--- a/net/quic/chromium/quic_chromium_client_session.cc
+++ b/net/quic/chromium/quic_chromium_client_session.cc
@@ -237,8 +237,8 @@ QuicChromiumClientSession::QuicChromiumClientSession(
token_binding_signatures_(kTokenBindingSignatureMapSize),
streams_pushed_count_(0),
streams_pushed_and_claimed_count_(0),
- error_code_from_rewrite_(OK),
- use_error_code_from_rewrite_(false),
+ migration_pending_(false),
+ write_pending_(false),
weak_factory_(this) {
sockets_.push_back(std::move(socket));
packet_readers_.push_back(base::WrapUnique(new QuicChromiumPacketReader(
@@ -952,11 +952,89 @@ int QuicChromiumClientSession::HandleWriteError(
int error_code,
scoped_refptr<StringIOBuffer> packet) {
DCHECK(packet != nullptr);
- use_error_code_from_rewrite_ = false;
- if (stream_factory_) {
- stream_factory_->MaybeMigrateSingleSession(this, WRITE_ERROR, packet);
+ DCHECK_NE(ERR_IO_PENDING, error_code);
+ DCHECK_GT(0, error_code);
+ DCHECK(!migration_pending_);
+ DCHECK(packet_ == nullptr);
+
+ // Post a task to migrate the session onto a new network.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuicChromiumClientSession::MigrateSessionOnWriteError,
+ weak_factory_.GetWeakPtr()));
+
+ // Store packet in the session since the actual migration and packet rewrite
+ // can happen via this posted task or via an async network notification.
+ packet_ = packet;
+ migration_pending_ = true;
Ryan Hamilton 2016/09/12 00:52:18 This is the only place migration_pending_ is set t
Jana 2016/09/12 21:08:02 This is used (later in the pause CL) by the NCN co
+ static_cast<QuicChromiumPacketWriter*>(connection()->writer())
+ ->set_write_blocked(true);
Ryan Hamilton 2016/09/12 00:52:18 This is not needed is it? By returning ERR_IO_PEND
Jana 2016/09/12 21:08:02 Oh of course -- I am not sure why I added this her
+
+ // Cause the packet writer to return ERR_IO_PENDING and block so
+ // that the actual migration happens from the message loop instead
+ // of under the call stack of QuicConnection::WritePacket.
+ return ERR_IO_PENDING;
+}
+
+void QuicChromiumClientSession::MigrateSessionOnWriteError() {
+ // If migration_pending_ is false, an earlier task completed migration.
+ if (!migration_pending_)
+ return;
+
+ if (stream_factory_ != nullptr &&
+ stream_factory_->MaybeMigrateSingleSession(this, WRITE_ERROR) ==
+ MigrationResult::SUCCESS)
+ return;
Ryan Hamilton 2016/09/12 00:52:18 nit: {}s needed.
Jana 2016/09/12 21:08:02 Done.
+
+ // Close the connection if migration failed. Do not cause a
+ // connection close packet to be sent since socket may be borked.
+ connection()->CloseConnection(QUIC_PACKET_WRITE_ERROR,
+ "Write and subsequent migration failed",
+ ConnectionCloseBehavior::SILENT_CLOSE);
+}
+
+void QuicChromiumClientSession::WriteToNewSocket() {
+ // If write_pending_ is false, an earlier task wrote to the new socket.
+ if (!write_pending_)
+ return;
Ryan Hamilton 2016/09/12 01:32:57 You can take this or leave it... If we removed thi
Jana 2016/09/12 21:08:02 I did have the code doing that earlier, but enforc
+ // Prevent any pending migration or write tasks from executing.
+ write_pending_ = false;
+ migration_pending_ = false;
+
+ static_cast<QuicChromiumPacketWriter*>(connection()->writer())
+ ->set_write_blocked(false);
+ DCHECK(!connection()->writer()->IsWriteBlocked());
+
+ if (packet_ == nullptr) {
+ connection()->SendPing();
+ // The connection may have been blocked before the migration
+ // started. Unblock the connection if sending the PING packet did
+ // not leave the writer blocked.
Ryan Hamilton 2016/09/12 00:52:18 I think the best thing to do is to call OnCanWrite
Jana 2016/09/12 21:08:02 Hm. Yeah, this does make reasoning simpler. Done.
+ if (!connection()->writer()->IsWriteBlocked())
+ connection()->OnCanWrite();
+ return;
}
- return use_error_code_from_rewrite_ ? error_code_from_rewrite_ : error_code;
+
+ // Set packet_ to null first. We cannot set packet_ to null after
+ // the following write since the write may result in packet_ being
+ // reused via a write error.
Ryan Hamilton 2016/09/12 00:52:18 Avoid first person in comments: // Set packet t
Jana 2016/09/12 21:08:02 Done.
+ scoped_refptr<StringIOBuffer> packet = packet_;
+ packet_ = nullptr;
+
+ // The connection is waiting for the original write to complete
+ // asynchronously. The new writer will notify the connection if the
+ // write below completes asynchronously, but a synchronous competion
+ // must be propagated back to the connection here.
+ WriteResult result =
+ static_cast<QuicChromiumPacketWriter*>(connection()->writer())
+ ->WritePacketToSocket(packet);
+
+ if (result.error_code == ERR_IO_PENDING)
+ return;
+ // All write errors should be mapped into ERR_IO_PENDING by
+ // HandleWriteError.
+ DCHECK(result.error_code >= 0);
Ryan Hamilton 2016/09/12 00:52:18 nit: DCHECK_LT(0, result.error_code);
Jana 2016/09/12 21:08:02 Done.
+ connection()->OnCanWrite();
}
void QuicChromiumClientSession::OnWriteError(int error_code) {
@@ -971,7 +1049,7 @@ void QuicChromiumClientSession::OnWriteUnblocked() {
void QuicChromiumClientSession::OnPathDegrading() {
if (stream_factory_) {
- stream_factory_->MaybeMigrateSingleSession(this, EARLY_MIGRATION, nullptr);
+ stream_factory_->MaybeMigrateSingleSession(this, EARLY_MIGRATION);
}
}
@@ -1178,25 +1256,31 @@ void QuicChromiumClientSession::NotifyFactoryOfSessionClosed() {
bool QuicChromiumClientSession::MigrateToSocket(
std::unique_ptr<DatagramClientSocket> socket,
std::unique_ptr<QuicChromiumPacketReader> reader,
- std::unique_ptr<QuicChromiumPacketWriter> writer,
- scoped_refptr<StringIOBuffer> packet) {
+ std::unique_ptr<QuicChromiumPacketWriter> writer) {
DCHECK_EQ(sockets_.size(), packet_readers_.size());
- if (sockets_.size() >= kMaxReadersPerQuicSession) {
+ if (sockets_.size() >= kMaxReadersPerQuicSession)
return false;
- }
+
// TODO(jri): Make SetQuicPacketWriter take a scoped_ptr.
packet_readers_.push_back(std::move(reader));
sockets_.push_back(std::move(socket));
StartReading();
- QuicChromiumPacketWriter* raw_writer = writer.get();
connection()->SetQuicPacketWriter(writer.release(), /*owns_writer=*/true);
- if (packet == nullptr) {
- connection()->SendPing();
- return true;
- }
- // Packet rewrite after migration on socket write error.
- error_code_from_rewrite_ = raw_writer->WritePacketToSocket(packet.get());
- use_error_code_from_rewrite_ = true;
+
+ // Post task to write the pending packet or a PING packet to the new
+ // socket. Also block the writer to prevent is being used until
Ryan Hamilton 2016/09/12 00:52:18 Can you add some language here which explains that
Jana 2016/09/12 21:08:02 Done.
+ // WriteToNewSocket completes.
Ryan Hamilton 2016/09/12 00:52:18 nit: move this sentence down to the call to block
Jana 2016/09/12 21:08:02 Done.
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&QuicChromiumClientSession::WriteToNewSocket,
+ weak_factory_.GetWeakPtr()));
+
+ // Block the writer until the task posted is executed.
+ static_cast<QuicChromiumPacketWriter*>(connection()->writer())
+ ->set_write_blocked(true);
+
+ // Migration completed and write task posted.
+ migration_pending_ = false;
+ write_pending_ = true;
return true;
}

Powered by Google App Engine
This is Rietveld 408576698