mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 09:45:41 +00:00
44ca650a2b
Previously, we had not put pushed streams in the priority tree, we just let them be top-level items in the tree. With this change, we will put them into the tree initially based on the priority of the associated stream. The only exception is if the associated stream is either a Leader or Urgent Start (in which case, we will turn the pushed streams into followers). Once the pushed stream is matched with a request generated by gecko, that pushed stream will be re-prioritized based on the priority gecko has for the request, just like a regular pulled stream. This also allows us to re-prioritize pushed streams into the background on tab switch (we assume that, before they are matched, they belong to the same window as the associated stream). Differential Revision: https://phabricator.services.mozilla.com/D7223 --HG-- extra : moz-landing-system : lando
540 lines
14 KiB
C++
540 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=8 et tw=80 : */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// HttpLog.h should generally be included first
|
|
#include "HttpLog.h"
|
|
|
|
// Log on level :5, instead of default :4.
|
|
#undef LOG
|
|
#define LOG(args) LOG5(args)
|
|
#undef LOG_ENABLED
|
|
#define LOG_ENABLED() LOG5_ENABLED()
|
|
|
|
#include <algorithm>
|
|
|
|
#include "Http2Push.h"
|
|
#include "nsHttpChannel.h"
|
|
#include "nsIHttpPushListener.h"
|
|
#include "nsString.h"
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
class CallChannelOnPush final : public Runnable {
|
|
public:
|
|
CallChannelOnPush(nsIHttpChannelInternal* associatedChannel,
|
|
const nsACString& pushedURI,
|
|
Http2PushedStream* pushStream)
|
|
: Runnable("net::CallChannelOnPush")
|
|
, mAssociatedChannel(associatedChannel)
|
|
, mPushedURI(pushedURI)
|
|
, mPushedStream(pushStream)
|
|
{
|
|
}
|
|
|
|
NS_IMETHOD Run() override
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
RefPtr<nsHttpChannel> channel;
|
|
CallQueryInterface(mAssociatedChannel, channel.StartAssignment());
|
|
MOZ_ASSERT(channel);
|
|
if (channel && NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStream))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this));
|
|
mPushedStream->OnPushFailed();
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel;
|
|
const nsCString mPushedURI;
|
|
Http2PushedStream *mPushedStream;
|
|
};
|
|
|
|
//////////////////////////////////////////
|
|
// Http2PushedStream
|
|
//////////////////////////////////////////
|
|
|
|
Http2PushedStream::Http2PushedStream(Http2PushTransactionBuffer *aTransaction,
|
|
Http2Session *aSession,
|
|
Http2Stream *aAssociatedStream,
|
|
uint32_t aID,
|
|
uint64_t aCurrentForegroundTabOuterContentWindowId)
|
|
:Http2Stream(aTransaction, aSession, 0, aCurrentForegroundTabOuterContentWindowId)
|
|
, mConsumerStream(nullptr)
|
|
, mAssociatedTransaction(aAssociatedStream->Transaction())
|
|
, mBufferedPush(aTransaction)
|
|
, mStatus(NS_OK)
|
|
, mPushCompleted(false)
|
|
, mDeferCleanupOnSuccess(true)
|
|
, mDeferCleanupOnPush(false)
|
|
, mOnPushFailed(false)
|
|
{
|
|
LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID));
|
|
mStreamID = aID;
|
|
MOZ_ASSERT(!(aID & 1)); // must be even to be a pushed stream
|
|
mBufferedPush->SetPushStream(this);
|
|
mRequestContext = aAssociatedStream->RequestContext();
|
|
mLastRead = TimeStamp::Now();
|
|
mPriorityDependency = aAssociatedStream->PriorityDependency();
|
|
if (mPriorityDependency == Http2Session::kUrgentStartGroupID ||
|
|
mPriorityDependency == Http2Session::kLeaderGroupID) {
|
|
mPriorityDependency = Http2Session::kFollowerGroupID;
|
|
}
|
|
// Cache this for later use in case of tab switch.
|
|
mDefaultPriorityDependency = mPriorityDependency;
|
|
SetPriorityDependency(aAssociatedStream->Priority() + 1, mPriorityDependency);
|
|
// Assume we are on the same tab as our associated stream, for priority purposes.
|
|
// It's possible this could change when we get paired with a sink, but it's unlikely
|
|
// and doesn't much matter anyway.
|
|
mTransactionTabId = aAssociatedStream->TransactionTabId();
|
|
}
|
|
|
|
bool
|
|
Http2PushedStream::GetPushComplete()
|
|
{
|
|
return mPushCompleted;
|
|
}
|
|
|
|
nsresult
|
|
Http2PushedStream::WriteSegments(nsAHttpSegmentWriter *writer,
|
|
uint32_t count, uint32_t *countWritten)
|
|
{
|
|
nsresult rv = Http2Stream::WriteSegments(writer, count, countWritten);
|
|
if (NS_SUCCEEDED(rv) && *countWritten) {
|
|
mLastRead = TimeStamp::Now();
|
|
}
|
|
|
|
if (rv == NS_BASE_STREAM_CLOSED) {
|
|
mPushCompleted = true;
|
|
rv = NS_OK; // this is what a normal HTTP transaction would do
|
|
}
|
|
if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv))
|
|
mStatus = rv;
|
|
return rv;
|
|
}
|
|
|
|
bool
|
|
Http2PushedStream::DeferCleanup(nsresult status)
|
|
{
|
|
LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 "\n", this,
|
|
static_cast<uint32_t>(status)));
|
|
|
|
if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) {
|
|
LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer on success\n", this,
|
|
static_cast<uint32_t>(status)));
|
|
return true;
|
|
}
|
|
if (mDeferCleanupOnPush) {
|
|
LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer onPush ref\n", this,
|
|
static_cast<uint32_t>(status)));
|
|
return true;
|
|
}
|
|
if (mConsumerStream) {
|
|
LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer active consumer\n", this,
|
|
static_cast<uint32_t>(status)));
|
|
return true;
|
|
}
|
|
LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 " not deferred\n", this,
|
|
static_cast<uint32_t>(status)));
|
|
return false;
|
|
}
|
|
|
|
// return true if channel implements nsIHttpPushListener
|
|
bool
|
|
Http2PushedStream::TryOnPush()
|
|
{
|
|
nsHttpTransaction *trans = mAssociatedTransaction->QueryHttpTransaction();
|
|
if (!trans) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel());
|
|
if (!associatedChannel) {
|
|
return false;
|
|
}
|
|
|
|
if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) {
|
|
return false;
|
|
}
|
|
|
|
mDeferCleanupOnPush = true;
|
|
nsCString uri = Origin() + Path();
|
|
NS_DispatchToMainThread(new CallChannelOnPush(associatedChannel, uri, this));
|
|
return true;
|
|
}
|
|
|
|
// side effect free static method to determine if Http2Stream implements nsIHttpPushListener
|
|
bool
|
|
Http2PushedStream::TestOnPush(Http2Stream *stream)
|
|
{
|
|
if (!stream) {
|
|
return false;
|
|
}
|
|
nsAHttpTransaction *abstractTransaction = stream->Transaction();
|
|
if (!abstractTransaction) {
|
|
return false;
|
|
}
|
|
nsHttpTransaction *trans = abstractTransaction->QueryHttpTransaction();
|
|
if (!trans) {
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel());
|
|
if (!associatedChannel) {
|
|
return false;
|
|
}
|
|
return (trans->Caps() & NS_HTTP_ONPUSH_LISTENER);
|
|
}
|
|
|
|
nsresult
|
|
Http2PushedStream::ReadSegments(nsAHttpSegmentReader *reader,
|
|
uint32_t, uint32_t *count)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
*count = 0;
|
|
|
|
mozilla::OriginAttributes originAttributes;
|
|
switch (mUpstreamState) {
|
|
case GENERATING_HEADERS:
|
|
// The request headers for this has been processed, so we need to verify
|
|
// that :authority, :scheme, and :path MUST be present. :method MUST NOT be
|
|
// present
|
|
mSocketTransport->GetOriginAttributes(&originAttributes);
|
|
CreatePushHashKey(mHeaderScheme, mHeaderHost, originAttributes,
|
|
mSession->Serial(), mHeaderPath,
|
|
mOrigin, mHashKey);
|
|
|
|
LOG3(("Http2PushStream 0x%X hash key %s\n", mStreamID, mHashKey.get()));
|
|
|
|
// the write side of a pushed transaction just involves manipulating a little state
|
|
SetSentFin(true);
|
|
Http2Stream::mRequestHeadersDone = 1;
|
|
Http2Stream::mOpenGenerated = 1;
|
|
Http2Stream::ChangeState(UPSTREAM_COMPLETE);
|
|
break;
|
|
|
|
case UPSTREAM_COMPLETE:
|
|
// Let's just clear the stream's transmit buffer by pushing it into
|
|
// the session. This is probably a window adjustment.
|
|
LOG3(("Http2Push::ReadSegments 0x%X \n", mStreamID));
|
|
mSegmentReader = reader;
|
|
rv = TransmitFrame(nullptr, nullptr, true);
|
|
mSegmentReader = nullptr;
|
|
break;
|
|
|
|
case GENERATING_BODY:
|
|
case SENDING_BODY:
|
|
case SENDING_FIN_STREAM:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
Http2PushedStream::AdjustInitialWindow()
|
|
{
|
|
LOG3(("Http2PushStream %p 0x%X AdjustInitialWindow", this, mStreamID));
|
|
if (mConsumerStream) {
|
|
LOG3(("Http2PushStream::AdjustInitialWindow %p 0x%X "
|
|
"calling super consumer %p 0x%X\n", this,
|
|
mStreamID, mConsumerStream, mConsumerStream->StreamID()));
|
|
Http2Stream::AdjustInitialWindow();
|
|
// Http2PushedStream::ReadSegments is needed to call TransmitFrame()
|
|
// and actually get this information into the session bytestream
|
|
mSession->TransactionHasDataToWrite(this);
|
|
}
|
|
// Otherwise, when we get hooked up, the initial window will get bumped
|
|
// anyway, so we're good to go.
|
|
}
|
|
|
|
void
|
|
Http2PushedStream::SetConsumerStream(Http2Stream *consumer)
|
|
{
|
|
LOG3(("Http2PushedStream::SetConsumerStream this=%p consumer=%p",
|
|
this, consumer));
|
|
|
|
mConsumerStream = consumer;
|
|
mDeferCleanupOnPush = false;
|
|
}
|
|
|
|
bool
|
|
Http2PushedStream::GetHashKey(nsCString &key)
|
|
{
|
|
if (mHashKey.IsEmpty())
|
|
return false;
|
|
|
|
key = mHashKey;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Http2PushedStream::ConnectPushedStream(Http2Stream *stream)
|
|
{
|
|
mSession->ConnectPushedStream(stream);
|
|
}
|
|
|
|
bool
|
|
Http2PushedStream::IsOrphaned(TimeStamp now)
|
|
{
|
|
MOZ_ASSERT(!now.IsNull());
|
|
|
|
// if session is not transmitting, and is also not connected to a consumer
|
|
// stream, and its been like that for too long then it is oprhaned
|
|
|
|
if (mConsumerStream || mDeferCleanupOnPush) {
|
|
return false;
|
|
}
|
|
|
|
if (mOnPushFailed) {
|
|
return true;
|
|
}
|
|
|
|
bool rv = ((now - mLastRead).ToSeconds() > 30.0);
|
|
if (rv) {
|
|
LOG3(("Http2PushedStream:IsOrphaned 0x%X IsOrphaned %3.2f\n",
|
|
mStreamID, (now - mLastRead).ToSeconds()));
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
Http2PushedStream::GetBufferedData(char *buf,
|
|
uint32_t count, uint32_t *countWritten)
|
|
{
|
|
if (NS_FAILED(mStatus))
|
|
return mStatus;
|
|
|
|
nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
if (!*countWritten)
|
|
rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
Http2PushedStream::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
|
|
{
|
|
if (mConsumerStream) {
|
|
// Pass through to our sink, who will handle things appropriately.
|
|
mConsumerStream->TopLevelOuterContentWindowIdChangedInternal(windowId);
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
|
|
|
|
mCurrentForegroundTabOuterContentWindowId = windowId;
|
|
|
|
if (!mSession->UseH2Deps()) {
|
|
return;
|
|
}
|
|
|
|
uint32_t oldDependency = mPriorityDependency;
|
|
if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId) {
|
|
mPriorityDependency = Http2Session::kBackgroundGroupID;
|
|
nsHttp::NotifyActiveTabLoadOptimization();
|
|
} else {
|
|
mPriorityDependency = mDefaultPriorityDependency;
|
|
}
|
|
|
|
if (mPriorityDependency != oldDependency) {
|
|
mSession->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
// Http2PushTransactionBuffer
|
|
// This is the nsAHttpTransction owned by the stream when the pushed
|
|
// stream has not yet been matched with a pull request
|
|
//////////////////////////////////////////
|
|
|
|
NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer)
|
|
|
|
Http2PushTransactionBuffer::Http2PushTransactionBuffer()
|
|
: mStatus(NS_OK)
|
|
, mRequestHead(nullptr)
|
|
, mPushStream(nullptr)
|
|
, mIsDone(false)
|
|
, mBufferedHTTP1Size(kDefaultBufferSize)
|
|
, mBufferedHTTP1Used(0)
|
|
, mBufferedHTTP1Consumed(0)
|
|
{
|
|
mBufferedHTTP1 = MakeUnique<char[]>(mBufferedHTTP1Size);
|
|
}
|
|
|
|
Http2PushTransactionBuffer::~Http2PushTransactionBuffer()
|
|
{
|
|
delete mRequestHead;
|
|
}
|
|
|
|
void
|
|
Http2PushTransactionBuffer::SetConnection(nsAHttpConnection *conn)
|
|
{
|
|
}
|
|
|
|
nsAHttpConnection *
|
|
Http2PushTransactionBuffer::Connection()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
Http2PushTransactionBuffer::GetSecurityCallbacks(nsIInterfaceRequestor **outCB)
|
|
{
|
|
*outCB = nullptr;
|
|
}
|
|
|
|
void
|
|
Http2PushTransactionBuffer::OnTransportStatus(nsITransport* transport,
|
|
nsresult status, int64_t progress)
|
|
{
|
|
}
|
|
|
|
nsHttpConnectionInfo *
|
|
Http2PushTransactionBuffer::ConnectionInfo()
|
|
{
|
|
if (!mPushStream) {
|
|
return nullptr;
|
|
}
|
|
if (!mPushStream->Transaction()) {
|
|
return nullptr;
|
|
}
|
|
MOZ_ASSERT(mPushStream->Transaction() != this);
|
|
return mPushStream->Transaction()->ConnectionInfo();
|
|
}
|
|
|
|
bool
|
|
Http2PushTransactionBuffer::IsDone()
|
|
{
|
|
return mIsDone;
|
|
}
|
|
|
|
nsresult
|
|
Http2PushTransactionBuffer::Status()
|
|
{
|
|
return mStatus;
|
|
}
|
|
|
|
uint32_t
|
|
Http2PushTransactionBuffer::Caps()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Http2PushTransactionBuffer::SetDNSWasRefreshed()
|
|
{
|
|
}
|
|
|
|
uint64_t
|
|
Http2PushTransactionBuffer::Available()
|
|
{
|
|
return mBufferedHTTP1Used - mBufferedHTTP1Consumed;
|
|
}
|
|
|
|
nsresult
|
|
Http2PushTransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader,
|
|
uint32_t count, uint32_t *countRead)
|
|
{
|
|
*countRead = 0;
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult
|
|
Http2PushTransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer,
|
|
uint32_t count, uint32_t *countWritten)
|
|
{
|
|
if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) {
|
|
EnsureBuffer(mBufferedHTTP1,mBufferedHTTP1Size + kDefaultBufferSize,
|
|
mBufferedHTTP1Used, mBufferedHTTP1Size);
|
|
}
|
|
|
|
count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used);
|
|
nsresult rv = writer->OnWriteSegment(&mBufferedHTTP1[mBufferedHTTP1Used],
|
|
count, countWritten);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mBufferedHTTP1Used += *countWritten;
|
|
}
|
|
else if (rv == NS_BASE_STREAM_CLOSED) {
|
|
mIsDone = true;
|
|
}
|
|
|
|
if (Available() || mIsDone) {
|
|
Http2Stream *consumer = mPushStream->GetConsumerStream();
|
|
|
|
if (consumer) {
|
|
LOG3(("Http2PushTransactionBuffer::WriteSegments notifying connection "
|
|
"consumer data available 0x%X [%" PRIu64 "] done=%d\n",
|
|
mPushStream->StreamID(), Available(), mIsDone));
|
|
mPushStream->ConnectPushedStream(consumer);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
uint32_t
|
|
Http2PushTransactionBuffer::Http1xTransactionCount()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
nsHttpRequestHead *
|
|
Http2PushTransactionBuffer::RequestHead()
|
|
{
|
|
if (!mRequestHead)
|
|
mRequestHead = new nsHttpRequestHead();
|
|
return mRequestHead;
|
|
}
|
|
|
|
nsresult
|
|
Http2PushTransactionBuffer::TakeSubTransactions(
|
|
nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
void
|
|
Http2PushTransactionBuffer::SetProxyConnectFailed()
|
|
{
|
|
}
|
|
|
|
void
|
|
Http2PushTransactionBuffer::Close(nsresult reason)
|
|
{
|
|
mStatus = reason;
|
|
mIsDone = true;
|
|
}
|
|
|
|
nsresult
|
|
Http2PushTransactionBuffer::GetBufferedData(char *buf,
|
|
uint32_t count,
|
|
uint32_t *countWritten)
|
|
{
|
|
*countWritten = std::min(count, static_cast<uint32_t>(Available()));
|
|
if (*countWritten) {
|
|
memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten);
|
|
mBufferedHTTP1Consumed += *countWritten;
|
|
}
|
|
|
|
// If all the data has been consumed then reset the buffer
|
|
if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) {
|
|
mBufferedHTTP1Consumed = 0;
|
|
mBufferedHTTP1Used = 0;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|