@@ -1405,13 +1405,6 @@ BaseObjectPtr<Blob::Reader> Stream::get_reader() {
14051405 return reader;
14061406}
14071407
1408- void Stream::set_final_size (uint64_t final_size) {
1409- DCHECK_IMPLIES (state ()->fin_received == 1 ,
1410- final_size <= STAT_GET (Stats, final_size));
1411- state ()->fin_received = 1 ;
1412- STAT_SET (Stats, final_size, final_size);
1413- }
1414-
14151408void Stream::set_outbound (std::shared_ptr<DataQueue> source) {
14161409 if (!source || !is_writable ()) return ;
14171410 Debug (this , " Setting the outbound data source" );
@@ -1607,13 +1600,20 @@ void Stream::EndWritable() {
16071600 state ()->write_ended = 1 ;
16081601}
16091602
1610- void Stream::EndReadable (std::optional<uint64_t > maybe_final_size) {
1603+ void Stream::EndReadable (std::optional<uint64_t > maybe_final_size,
1604+ bool clean_fin) {
16111605 if (!is_readable ()) return ;
16121606 state ()->read_ended = 1 ;
1607+ // fin_received marks a clean completion of the read side (a real FIN). Any
1608+ // unclean end (reset/abort/session teardown) truncates the stream, which
1609+ // the JS reader will expose as a read error later.
1610+ if (clean_fin) state ()->fin_received = 1 ;
16131611 // Flush any accumulated data before capping so the reader can see it.
16141612 FlushAccumulation ();
1615- set_final_size (maybe_final_size.value_or (STAT_GET (Stats, bytes_received)));
1616- inbound_->cap (STAT_GET (Stats, final_size));
1613+ const uint64_t final_size =
1614+ maybe_final_size.value_or (STAT_GET (Stats, bytes_received));
1615+ STAT_SET (Stats, final_size, final_size);
1616+ inbound_->cap (final_size);
16171617 // Notify the JS reader so it can see EOS. Pass fin=true so the
16181618 // wakeup promise resolves with a value the iterator can check to
16191619 // avoid waiting for another wakeup that will never come.
@@ -1642,8 +1642,9 @@ void Stream::Destroy(QuicError error) {
16421642 // End the writable before marking as destroyed.
16431643 EndWritable ();
16441644
1645- // Also end the readable side if it isn't already.
1646- EndReadable ();
1645+ // Also end the readable side if it isn't already. If not already ended,
1646+ // this will eventually surface as a error, since the data is truncated.
1647+ EndReadable (std::nullopt , /* clean_fin = */ false );
16471648
16481649 // We are going to release our reference to the outbound_ queue here.
16491650 outbound_.reset ();
@@ -1690,7 +1691,7 @@ void Stream::ReceiveData(const uint8_t* data,
16901691 // end the readable side if this is the last bit of data we've received.
16911692 Debug (this , " Receiving %zu bytes of data" , len);
16921693 if (state ()->read_ended == 1 || len == 0 ) {
1693- if (flags.fin ) EndReadable ();
1694+ if (flags.fin ) EndReadable (std:: nullopt , /* clean_fin = */ true );
16941695 return ;
16951696 }
16961697
@@ -1763,7 +1764,7 @@ void Stream::ReceiveData(const uint8_t* data,
17631764
17641765 if (flags.fin ) {
17651766 FlushAccumulation ();
1766- EndReadable ();
1767+ EndReadable (std:: nullopt , /* clean_fin = */ true );
17671768 } else if (reader_ && was_empty) {
17681769 // Notify the reader once when the accumulator transitions from empty
17691770 // to non-empty. This wakes the reader exactly once per accumulation
@@ -1794,7 +1795,7 @@ void Stream::ReceiveStreamReset(uint64_t final_size, QuicError error) {
17941795 final_size,
17951796 error);
17961797 state ()->reset_code = error.code ();
1797- EndReadable (final_size);
1798+ EndReadable (final_size, /* clean_fin = */ false );
17981799 EmitReset (error);
17991800}
18001801
@@ -1817,7 +1818,7 @@ void Stream::DoStreamReset(error_code code) {
18171818}
18181819
18191820void Stream::SendStopSending (error_code code) {
1820- EndReadable ();
1821+ EndReadable (std:: nullopt , /* clean_fin = */ false );
18211822
18221823 if (!is_pending ()) {
18231824 // If the stream is a local unidirectional there's nothing to do here.
0 commit comments