blob: 1c7bc0a880510fdde7b12486635664d47872c099 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/spdy/spdy_write_queue.h"
#include <cstddef>
#include <cstring>
#include <string>
#include <utility>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "net/base/request_priority.h"
#include "net/log/net_log.h"
#include "net/spdy/spdy_buffer_producer.h"
#include "net/spdy/spdy_stream.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace net {
namespace {
using std::string;
const char kOriginal[] = "original";
const char kRequeued[] = "requeued";
class SpdyWriteQueueTest : public ::testing::Test {};
// Makes a SpdyFrameProducer producing a frame with the data in the
// given string.
scoped_ptr<SpdyBufferProducer> StringToProducer(const std::string& s) {
scoped_ptr<char[]> data(new char[s.size()]);
std::memcpy(data.get(), s.data(), s.size());
return scoped_ptr<SpdyBufferProducer>(
new SimpleBufferProducer(
scoped_ptr<SpdyBuffer>(
new SpdyBuffer(
scoped_ptr<SpdyFrame>(
new SpdyFrame(data.release(), s.size(), true))))));
}
// Makes a SpdyBufferProducer producing a frame with the data in the
// given int (converted to a string).
scoped_ptr<SpdyBufferProducer> IntToProducer(int i) {
return StringToProducer(base::IntToString(i));
}
// Producer whose produced buffer will enqueue yet another buffer into the
// SpdyWriteQueue upon destruction.
class RequeingBufferProducer : public SpdyBufferProducer {
public:
RequeingBufferProducer(SpdyWriteQueue* queue) {
buffer_.reset(new SpdyBuffer(kOriginal, arraysize(kOriginal)));
buffer_->AddConsumeCallback(
base::Bind(RequeingBufferProducer::ConsumeCallback, queue));
}
scoped_ptr<SpdyBuffer> ProduceBuffer() override { return std::move(buffer_); }
static void ConsumeCallback(SpdyWriteQueue* queue,
size_t size,
SpdyBuffer::ConsumeSource source) {
scoped_ptr<SpdyBufferProducer> producer(
new SimpleBufferProducer(scoped_ptr<SpdyBuffer>(
new SpdyBuffer(kRequeued, arraysize(kRequeued)))));
queue->Enqueue(MEDIUM, RST_STREAM, std::move(producer),
base::WeakPtr<SpdyStream>());
}
private:
scoped_ptr<SpdyBuffer> buffer_;
};
// Produces a frame with the given producer and returns a copy of its
// data as a string.
std::string ProducerToString(scoped_ptr<SpdyBufferProducer> producer) {
scoped_ptr<SpdyBuffer> buffer = producer->ProduceBuffer();
return std::string(buffer->GetRemainingData(), buffer->GetRemainingSize());
}
// Produces a frame with the given producer and returns a copy of its
// data as an int (converted from a string).
int ProducerToInt(scoped_ptr<SpdyBufferProducer> producer) {
int i = 0;
EXPECT_TRUE(base::StringToInt(ProducerToString(std::move(producer)), &i));
return i;
}
// Makes a SpdyStream with the given priority and a NULL SpdySession
// -- be careful to not call any functions that expect the session to
// be there.
SpdyStream* MakeTestStream(RequestPriority priority) {
return new SpdyStream(
SPDY_BIDIRECTIONAL_STREAM, base::WeakPtr<SpdySession>(),
GURL(), priority, 0, 0, BoundNetLog());
}
// Add some frame producers of different priority. The producers
// should be dequeued in priority order with their associated stream.
TEST_F(SpdyWriteQueueTest, DequeuesByPriority) {
SpdyWriteQueue write_queue;
scoped_ptr<SpdyBufferProducer> producer_low = StringToProducer("LOW");
scoped_ptr<SpdyBufferProducer> producer_medium = StringToProducer("MEDIUM");
scoped_ptr<SpdyBufferProducer> producer_highest = StringToProducer("HIGHEST");
scoped_ptr<SpdyStream> stream_medium(MakeTestStream(MEDIUM));
scoped_ptr<SpdyStream> stream_highest(MakeTestStream(HIGHEST));
// A NULL stream should still work.
write_queue.Enqueue(LOW, SYN_STREAM, std::move(producer_low),
base::WeakPtr<SpdyStream>());
write_queue.Enqueue(MEDIUM, SYN_REPLY, std::move(producer_medium),
stream_medium->GetWeakPtr());
write_queue.Enqueue(HIGHEST, RST_STREAM, std::move(producer_highest),
stream_highest->GetWeakPtr());
SpdyFrameType frame_type = DATA;
scoped_ptr<SpdyBufferProducer> frame_producer;
base::WeakPtr<SpdyStream> stream;
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
EXPECT_EQ(RST_STREAM, frame_type);
EXPECT_EQ("HIGHEST", ProducerToString(std::move(frame_producer)));
EXPECT_EQ(stream_highest.get(), stream.get());
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
EXPECT_EQ(SYN_REPLY, frame_type);
EXPECT_EQ("MEDIUM", ProducerToString(std::move(frame_producer)));
EXPECT_EQ(stream_medium.get(), stream.get());
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
EXPECT_EQ(SYN_STREAM, frame_type);
EXPECT_EQ("LOW", ProducerToString(std::move(frame_producer)));
EXPECT_EQ(nullptr, stream.get());
EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
}
// Add some frame producers with the same priority. The producers
// should be dequeued in FIFO order with their associated stream.
TEST_F(SpdyWriteQueueTest, DequeuesFIFO) {
SpdyWriteQueue write_queue;
scoped_ptr<SpdyBufferProducer> producer1 = IntToProducer(1);
scoped_ptr<SpdyBufferProducer> producer2 = IntToProducer(2);
scoped_ptr<SpdyBufferProducer> producer3 = IntToProducer(3);
scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY));
scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY));
scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY));
write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, std::move(producer1),
stream1->GetWeakPtr());
write_queue.Enqueue(DEFAULT_PRIORITY, SYN_REPLY, std::move(producer2),
stream2->GetWeakPtr());
write_queue.Enqueue(DEFAULT_PRIORITY, RST_STREAM, std::move(producer3),
stream3->GetWeakPtr());
SpdyFrameType frame_type = DATA;
scoped_ptr<SpdyBufferProducer> frame_producer;
base::WeakPtr<SpdyStream> stream;
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
EXPECT_EQ(SYN_STREAM, frame_type);
EXPECT_EQ(1, ProducerToInt(std::move(frame_producer)));
EXPECT_EQ(stream1.get(), stream.get());
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
EXPECT_EQ(SYN_REPLY, frame_type);
EXPECT_EQ(2, ProducerToInt(std::move(frame_producer)));
EXPECT_EQ(stream2.get(), stream.get());
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
EXPECT_EQ(RST_STREAM, frame_type);
EXPECT_EQ(3, ProducerToInt(std::move(frame_producer)));
EXPECT_EQ(stream3.get(), stream.get());
EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
}
// Enqueue a bunch of writes and then call
// RemovePendingWritesForStream() on one of the streams. No dequeued
// write should be for that stream.
TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStream) {
SpdyWriteQueue write_queue;
scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY));
scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY));
for (int i = 0; i < 100; ++i) {
base::WeakPtr<SpdyStream> stream =
(((i % 3) == 0) ? stream1 : stream2)->GetWeakPtr();
write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), stream);
}
write_queue.RemovePendingWritesForStream(stream2->GetWeakPtr());
for (int i = 0; i < 100; i += 3) {
SpdyFrameType frame_type = DATA;
scoped_ptr<SpdyBufferProducer> frame_producer;
base::WeakPtr<SpdyStream> stream;
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
EXPECT_EQ(SYN_STREAM, frame_type);
EXPECT_EQ(i, ProducerToInt(std::move(frame_producer)));
EXPECT_EQ(stream1.get(), stream.get());
}
SpdyFrameType frame_type = DATA;
scoped_ptr<SpdyBufferProducer> frame_producer;
base::WeakPtr<SpdyStream> stream;
EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
}
// Enqueue a bunch of writes and then call
// RemovePendingWritesForStreamsAfter(). No dequeued write should be for
// those streams without a stream id, or with a stream_id after that
// argument.
TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStreamsAfter) {
SpdyWriteQueue write_queue;
scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY));
stream1->set_stream_id(1);
scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY));
stream2->set_stream_id(3);
scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY));
stream3->set_stream_id(5);
// No stream id assigned.
scoped_ptr<SpdyStream> stream4(MakeTestStream(DEFAULT_PRIORITY));
base::WeakPtr<SpdyStream> streams[] = {
stream1->GetWeakPtr(), stream2->GetWeakPtr(),
stream3->GetWeakPtr(), stream4->GetWeakPtr()
};
for (int i = 0; i < 100; ++i) {
write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i),
streams[i % arraysize(streams)]);
}
write_queue.RemovePendingWritesForStreamsAfter(stream1->stream_id());
for (int i = 0; i < 100; i += arraysize(streams)) {
SpdyFrameType frame_type = DATA;
scoped_ptr<SpdyBufferProducer> frame_producer;
base::WeakPtr<SpdyStream> stream;
ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream))
<< "Unable to Dequeue i: " << i;
EXPECT_EQ(SYN_STREAM, frame_type);
EXPECT_EQ(i, ProducerToInt(std::move(frame_producer)));
EXPECT_EQ(stream1.get(), stream.get());
}
SpdyFrameType frame_type = DATA;
scoped_ptr<SpdyBufferProducer> frame_producer;
base::WeakPtr<SpdyStream> stream;
EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
}
// Enqueue a bunch of writes and then call Clear(). The write queue
// should clean up the memory properly, and Dequeue() should return
// false.
TEST_F(SpdyWriteQueueTest, Clear) {
SpdyWriteQueue write_queue;
for (int i = 0; i < 100; ++i) {
write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i),
base::WeakPtr<SpdyStream>());
}
write_queue.Clear();
SpdyFrameType frame_type = DATA;
scoped_ptr<SpdyBufferProducer> frame_producer;
base::WeakPtr<SpdyStream> stream;
EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
}
TEST_F(SpdyWriteQueueTest, RequeingProducerWithoutReentrance) {
SpdyWriteQueue queue;
queue.Enqueue(
DEFAULT_PRIORITY,
SYN_STREAM,
scoped_ptr<SpdyBufferProducer>(new RequeingBufferProducer(&queue)),
base::WeakPtr<SpdyStream>());
{
SpdyFrameType frame_type;
scoped_ptr<SpdyBufferProducer> producer;
base::WeakPtr<SpdyStream> stream;
EXPECT_TRUE(queue.Dequeue(&frame_type, &producer, &stream));
EXPECT_TRUE(queue.IsEmpty());
EXPECT_EQ(string(kOriginal), producer->ProduceBuffer()->GetRemainingData());
}
// |producer| was destroyed, and a buffer is re-queued.
EXPECT_FALSE(queue.IsEmpty());
SpdyFrameType frame_type;
scoped_ptr<SpdyBufferProducer> producer;
base::WeakPtr<SpdyStream> stream;
EXPECT_TRUE(queue.Dequeue(&frame_type, &producer, &stream));
EXPECT_EQ(string(kRequeued), producer->ProduceBuffer()->GetRemainingData());
}
TEST_F(SpdyWriteQueueTest, ReentranceOnClear) {
SpdyWriteQueue queue;
queue.Enqueue(
DEFAULT_PRIORITY,
SYN_STREAM,
scoped_ptr<SpdyBufferProducer>(new RequeingBufferProducer(&queue)),
base::WeakPtr<SpdyStream>());
queue.Clear();
EXPECT_FALSE(queue.IsEmpty());
SpdyFrameType frame_type;
scoped_ptr<SpdyBufferProducer> producer;
base::WeakPtr<SpdyStream> stream;
EXPECT_TRUE(queue.Dequeue(&frame_type, &producer, &stream));
EXPECT_EQ(string(kRequeued), producer->ProduceBuffer()->GetRemainingData());
}
TEST_F(SpdyWriteQueueTest, ReentranceOnRemovePendingWritesAfter) {
scoped_ptr<SpdyStream> stream(MakeTestStream(DEFAULT_PRIORITY));
stream->set_stream_id(2);
SpdyWriteQueue queue;
queue.Enqueue(
DEFAULT_PRIORITY,
SYN_STREAM,
scoped_ptr<SpdyBufferProducer>(new RequeingBufferProducer(&queue)),
stream->GetWeakPtr());
queue.RemovePendingWritesForStreamsAfter(1);
EXPECT_FALSE(queue.IsEmpty());
SpdyFrameType frame_type;
scoped_ptr<SpdyBufferProducer> producer;
base::WeakPtr<SpdyStream> weak_stream;
EXPECT_TRUE(queue.Dequeue(&frame_type, &producer, &weak_stream));
EXPECT_EQ(string(kRequeued), producer->ProduceBuffer()->GetRemainingData());
}
TEST_F(SpdyWriteQueueTest, ReentranceOnRemovePendingWritesForStream) {
scoped_ptr<SpdyStream> stream(MakeTestStream(DEFAULT_PRIORITY));
stream->set_stream_id(2);
SpdyWriteQueue queue;
queue.Enqueue(
DEFAULT_PRIORITY,
SYN_STREAM,
scoped_ptr<SpdyBufferProducer>(new RequeingBufferProducer(&queue)),
stream->GetWeakPtr());
queue.RemovePendingWritesForStream(stream->GetWeakPtr());
EXPECT_FALSE(queue.IsEmpty());
SpdyFrameType frame_type;
scoped_ptr<SpdyBufferProducer> producer;
base::WeakPtr<SpdyStream> weak_stream;
EXPECT_TRUE(queue.Dequeue(&frame_type, &producer, &weak_stream));
EXPECT_EQ(string(kRequeued), producer->ProduceBuffer()->GetRemainingData());
}
} // namespace
} // namespace net