mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-04 12:47:36 +03:00
The queue holds all streams that have data to send. When stream.Write or stream.Close are called, a stream is added to this queue. It is removed from the queue when all available (at that moment) data was sent. This way, we don't need the round robin scheduling (which, for every packet sent, asked every single open stream if it had data) any more.
581 lines
20 KiB
Go
581 lines
20 KiB
Go
package quic
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/lucas-clemente/quic-go/internal/mocks"
|
|
"github.com/lucas-clemente/quic-go/internal/protocol"
|
|
"github.com/lucas-clemente/quic-go/internal/wire"
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
)
|
|
|
|
var _ = Describe("Send Stream", func() {
|
|
const streamID protocol.StreamID = 1337
|
|
|
|
var (
|
|
str *sendStream
|
|
strWithTimeout io.Writer // str wrapped with gbytes.TimeoutWriter
|
|
mockFC *mocks.MockStreamFlowController
|
|
mockSender *MockStreamSender
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
mockSender = NewMockStreamSender(mockCtrl)
|
|
mockFC = mocks.NewMockStreamFlowController(mockCtrl)
|
|
str = newSendStream(streamID, mockSender, mockFC, protocol.VersionWhatever)
|
|
|
|
timeout := scaleDuration(250 * time.Millisecond)
|
|
strWithTimeout = gbytes.TimeoutWriter(str, timeout)
|
|
})
|
|
|
|
It("gets stream id", func() {
|
|
Expect(str.StreamID()).To(Equal(protocol.StreamID(1337)))
|
|
})
|
|
|
|
Context("writing", func() {
|
|
It("writes and gets all data at once", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999))
|
|
mockFC.EXPECT().AddBytesSent(protocol.ByteCount(6))
|
|
mockFC.EXPECT().IsNewlyBlocked()
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
n, err := strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(6))
|
|
close(done)
|
|
}()
|
|
Consistently(done).ShouldNot(BeClosed())
|
|
var f *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame {
|
|
f, _ = str.popStreamFrame(1000)
|
|
return f
|
|
}).ShouldNot(BeNil())
|
|
Expect(f.Data).To(Equal([]byte("foobar")))
|
|
Expect(f.FinBit).To(BeFalse())
|
|
Expect(f.Offset).To(BeZero())
|
|
Expect(f.DataLenPresent).To(BeTrue())
|
|
Expect(str.writeOffset).To(Equal(protocol.ByteCount(6)))
|
|
Expect(str.dataForWriting).To(BeNil())
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("writes and gets data in two turns", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
frameHeaderLen := protocol.ByteCount(4)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999)).Times(2)
|
|
mockFC.EXPECT().AddBytesSent(gomock.Any() /* protocol.ByteCount(3)*/).Times(2)
|
|
mockFC.EXPECT().IsNewlyBlocked().Times(2)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
n, err := strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(6))
|
|
close(done)
|
|
}()
|
|
Consistently(done).ShouldNot(BeClosed())
|
|
var f *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame {
|
|
f, _ = str.popStreamFrame(3 + frameHeaderLen)
|
|
return f
|
|
}).ShouldNot(BeNil())
|
|
Expect(f.Data).To(Equal([]byte("foo")))
|
|
Expect(f.FinBit).To(BeFalse())
|
|
Expect(f.Offset).To(BeZero())
|
|
Expect(f.DataLenPresent).To(BeTrue())
|
|
f, _ = str.popStreamFrame(100)
|
|
Expect(f.Data).To(Equal([]byte("bar")))
|
|
Expect(f.FinBit).To(BeFalse())
|
|
Expect(f.Offset).To(Equal(protocol.ByteCount(3)))
|
|
Expect(f.DataLenPresent).To(BeTrue())
|
|
Expect(str.popStreamFrame(1000)).To(BeNil())
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("popStreamFrame returns nil if no data is available", func() {
|
|
frame, hasMoreData := str.popStreamFrame(1000)
|
|
Expect(frame).To(BeNil())
|
|
Expect(hasMoreData).To(BeFalse())
|
|
})
|
|
|
|
It("says if it has more data for writing", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999)).Times(2)
|
|
mockFC.EXPECT().AddBytesSent(gomock.Any()).Times(2)
|
|
mockFC.EXPECT().IsNewlyBlocked().Times(2)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
n, err := strWithTimeout.Write(bytes.Repeat([]byte{0}, 100))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(100))
|
|
close(done)
|
|
}()
|
|
var hasMoreData bool
|
|
Eventually(func() *wire.StreamFrame {
|
|
var frame *wire.StreamFrame
|
|
frame, hasMoreData = str.popStreamFrame(50)
|
|
return frame
|
|
}).ShouldNot(BeNil())
|
|
Expect(hasMoreData).To(BeTrue())
|
|
frame, hasMoreData := str.popStreamFrame(1000)
|
|
Expect(frame).ToNot(BeNil())
|
|
Expect(hasMoreData).To(BeFalse())
|
|
frame, _ = str.popStreamFrame(1000)
|
|
Expect(frame).To(BeNil())
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("copies the slice while writing", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
frameHeaderSize := protocol.ByteCount(4)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999)).Times(2)
|
|
mockFC.EXPECT().AddBytesSent(protocol.ByteCount(1))
|
|
mockFC.EXPECT().AddBytesSent(protocol.ByteCount(2))
|
|
mockFC.EXPECT().IsNewlyBlocked().Times(2)
|
|
s := []byte("foo")
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
n, err := strWithTimeout.Write(s)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(3))
|
|
}()
|
|
var frame *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame { frame, _ = str.popStreamFrame(frameHeaderSize + 1); return frame }).ShouldNot(BeNil())
|
|
Expect(frame.Data).To(Equal([]byte("f")))
|
|
s[1] = 'e'
|
|
f, _ := str.popStreamFrame(100)
|
|
Expect(f).ToNot(BeNil())
|
|
Expect(f.Data).To(Equal([]byte("oo")))
|
|
})
|
|
|
|
It("returns when given a nil input", func() {
|
|
n, err := strWithTimeout.Write(nil)
|
|
Expect(n).To(BeZero())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("returns when given an empty slice", func() {
|
|
n, err := strWithTimeout.Write([]byte(""))
|
|
Expect(n).To(BeZero())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("cancels the context when Close is called", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
Expect(str.Context().Done()).ToNot(BeClosed())
|
|
str.Close()
|
|
Expect(str.Context().Done()).To(BeClosed())
|
|
})
|
|
|
|
Context("adding BLOCKED", func() {
|
|
It("queues a BLOCKED frame if the stream is flow control blocked", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockSender.EXPECT().queueControlFrame(&wire.StreamBlockedFrame{
|
|
StreamID: streamID,
|
|
Offset: 10,
|
|
})
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999))
|
|
mockFC.EXPECT().AddBytesSent(protocol.ByteCount(6))
|
|
// don't use offset 6 here, to make sure the BLOCKED frame contains the number returned by the flow controller
|
|
mockFC.EXPECT().IsNewlyBlocked().Return(true, protocol.ByteCount(10))
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := str.Write([]byte("foobar"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
close(done)
|
|
}()
|
|
var f *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame {
|
|
f, _ = str.popStreamFrame(1000)
|
|
return f
|
|
}).ShouldNot(BeNil())
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("doesn't queue a BLOCKED frame if the stream is flow control blocked, but the frame popped has the FIN bit set", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID).Times(2) // once for the Write, once for the Close
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999))
|
|
mockFC.EXPECT().AddBytesSent(protocol.ByteCount(6))
|
|
// don't EXPECT a call to mockFC.IsNewlyBlocked
|
|
// don't EXPECT a call to mockSender.queueControlFrame
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := str.Write([]byte("foobar"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
close(done)
|
|
}()
|
|
Consistently(done).ShouldNot(BeClosed())
|
|
Expect(str.Close()).To(Succeed())
|
|
var f *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame {
|
|
f, _ = str.popStreamFrame(1000)
|
|
return f
|
|
}).ShouldNot(BeNil())
|
|
Expect(f.FinBit).To(BeTrue())
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
})
|
|
|
|
Context("deadlines", func() {
|
|
It("returns an error when Write is called after the deadline", func() {
|
|
str.SetWriteDeadline(time.Now().Add(-time.Second))
|
|
n, err := strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(n).To(BeZero())
|
|
})
|
|
|
|
It("unblocks after the deadline", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
deadline := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
str.SetWriteDeadline(deadline)
|
|
n, err := strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(n).To(BeZero())
|
|
Expect(time.Now()).To(BeTemporally("~", deadline, scaleDuration(20*time.Millisecond)))
|
|
})
|
|
|
|
It("returns the number of bytes written, when the deadline expires", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(10000)).AnyTimes()
|
|
mockFC.EXPECT().AddBytesSent(gomock.Any())
|
|
mockFC.EXPECT().IsNewlyBlocked()
|
|
deadline := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
str.SetWriteDeadline(deadline)
|
|
var n int
|
|
writeReturned := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
var err error
|
|
n, err = strWithTimeout.Write(bytes.Repeat([]byte{0}, 100))
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(time.Now()).To(BeTemporally("~", deadline, scaleDuration(20*time.Millisecond)))
|
|
close(writeReturned)
|
|
}()
|
|
var frame *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame {
|
|
frame, _ = str.popStreamFrame(50)
|
|
return frame
|
|
}).ShouldNot(BeNil())
|
|
Eventually(writeReturned, scaleDuration(80*time.Millisecond)).Should(BeClosed())
|
|
Expect(n).To(BeEquivalentTo(frame.DataLen()))
|
|
})
|
|
|
|
It("doesn't pop any data after the deadline expired", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(10000)).AnyTimes()
|
|
mockFC.EXPECT().AddBytesSent(gomock.Any())
|
|
mockFC.EXPECT().IsNewlyBlocked()
|
|
deadline := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
str.SetWriteDeadline(deadline)
|
|
writeReturned := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := strWithTimeout.Write(bytes.Repeat([]byte{0}, 100))
|
|
Expect(err).To(MatchError(errDeadline))
|
|
close(writeReturned)
|
|
}()
|
|
var frame *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame {
|
|
frame, _ = str.popStreamFrame(50)
|
|
return frame
|
|
}).ShouldNot(BeNil())
|
|
Eventually(writeReturned, scaleDuration(80*time.Millisecond)).Should(BeClosed())
|
|
frame, hasMoreData := str.popStreamFrame(50)
|
|
Expect(frame).To(BeNil())
|
|
Expect(hasMoreData).To(BeFalse())
|
|
})
|
|
|
|
It("doesn't unblock if the deadline is changed before the first one expires", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
deadline1 := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
deadline2 := time.Now().Add(scaleDuration(100 * time.Millisecond))
|
|
str.SetWriteDeadline(deadline1)
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
time.Sleep(scaleDuration(20 * time.Millisecond))
|
|
str.SetWriteDeadline(deadline2)
|
|
// make sure that this was actually execute before the deadline expires
|
|
Expect(time.Now()).To(BeTemporally("<", deadline1))
|
|
}()
|
|
runtime.Gosched()
|
|
n, err := strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(n).To(BeZero())
|
|
Expect(time.Now()).To(BeTemporally("~", deadline2, scaleDuration(20*time.Millisecond)))
|
|
})
|
|
|
|
It("unblocks earlier, when a new deadline is set", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
deadline1 := time.Now().Add(scaleDuration(200 * time.Millisecond))
|
|
deadline2 := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
time.Sleep(scaleDuration(10 * time.Millisecond))
|
|
str.SetWriteDeadline(deadline2)
|
|
// make sure that this was actually execute before the deadline expires
|
|
Expect(time.Now()).To(BeTemporally("<", deadline2))
|
|
}()
|
|
str.SetWriteDeadline(deadline1)
|
|
runtime.Gosched()
|
|
_, err := strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(time.Now()).To(BeTemporally("~", deadline2, scaleDuration(20*time.Millisecond)))
|
|
})
|
|
})
|
|
|
|
Context("closing", func() {
|
|
It("doesn't allow writes after it has been closed", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
str.Close()
|
|
_, err := strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError("write on closed stream 1337"))
|
|
})
|
|
|
|
It("allows FIN", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
str.Close()
|
|
f, hasMoreData := str.popStreamFrame(1000)
|
|
Expect(f).ToNot(BeNil())
|
|
Expect(f.Data).To(BeEmpty())
|
|
Expect(f.FinBit).To(BeTrue())
|
|
Expect(hasMoreData).To(BeFalse())
|
|
})
|
|
|
|
It("doesn't send a FIN when there's still data", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
frameHeaderLen := protocol.ByteCount(4)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999)).Times(2)
|
|
mockFC.EXPECT().AddBytesSent(gomock.Any()).Times(2)
|
|
mockFC.EXPECT().IsNewlyBlocked()
|
|
str.dataForWriting = []byte("foobar")
|
|
Expect(str.Close()).To(Succeed())
|
|
f, _ := str.popStreamFrame(3 + frameHeaderLen)
|
|
Expect(f).ToNot(BeNil())
|
|
Expect(f.Data).To(Equal([]byte("foo")))
|
|
Expect(f.FinBit).To(BeFalse())
|
|
f, _ = str.popStreamFrame(100)
|
|
Expect(f.Data).To(Equal([]byte("bar")))
|
|
Expect(f.FinBit).To(BeTrue())
|
|
})
|
|
|
|
It("doesn't allow FIN after it is closed for shutdown", func() {
|
|
str.closeForShutdown(errors.New("test"))
|
|
f, hasMoreData := str.popStreamFrame(1000)
|
|
Expect(f).To(BeNil())
|
|
Expect(hasMoreData).To(BeFalse())
|
|
})
|
|
|
|
It("doesn't allow FIN twice", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
str.Close()
|
|
f, _ := str.popStreamFrame(1000)
|
|
Expect(f).ToNot(BeNil())
|
|
Expect(f.Data).To(BeEmpty())
|
|
Expect(f.FinBit).To(BeTrue())
|
|
f, hasMoreData := str.popStreamFrame(1000)
|
|
Expect(f).To(BeNil())
|
|
Expect(hasMoreData).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("closing for shutdown", func() {
|
|
testErr := errors.New("test")
|
|
|
|
It("returns errors when the stream is cancelled", func() {
|
|
str.closeForShutdown(testErr)
|
|
n, err := strWithTimeout.Write([]byte("foo"))
|
|
Expect(n).To(BeZero())
|
|
Expect(err).To(MatchError(testErr))
|
|
})
|
|
|
|
It("doesn't get data for writing if an error occurred", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.ByteCount(9999))
|
|
mockFC.EXPECT().AddBytesSent(gomock.Any())
|
|
mockFC.EXPECT().IsNewlyBlocked()
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := strWithTimeout.Write(bytes.Repeat([]byte{0}, 500))
|
|
Expect(err).To(MatchError(testErr))
|
|
close(done)
|
|
}()
|
|
Eventually(func() *wire.StreamFrame {
|
|
defer GinkgoRecover()
|
|
// get a STREAM frame containing some data, but not all
|
|
frame, _ := str.popStreamFrame(50)
|
|
return frame
|
|
}).ShouldNot(BeNil())
|
|
str.closeForShutdown(testErr)
|
|
Expect(str.popStreamFrame(1000)).To(BeNil())
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("cancels the context", func() {
|
|
Expect(str.Context().Done()).ToNot(BeClosed())
|
|
str.closeForShutdown(testErr)
|
|
Expect(str.Context().Done()).To(BeClosed())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("stream cancelations", func() {
|
|
Context("canceling writing", func() {
|
|
It("queues a RST_STREAM frame", func() {
|
|
mockSender.EXPECT().queueControlFrame(&wire.RstStreamFrame{
|
|
StreamID: streamID,
|
|
ByteOffset: 1234,
|
|
ErrorCode: 9876,
|
|
})
|
|
str.writeOffset = 1234
|
|
err := str.CancelWrite(9876)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("unblocks Write", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
mockFC.EXPECT().SendWindowSize().Return(protocol.MaxByteCount)
|
|
mockFC.EXPECT().AddBytesSent(gomock.Any())
|
|
mockFC.EXPECT().IsNewlyBlocked()
|
|
writeReturned := make(chan struct{})
|
|
var n int
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
var err error
|
|
n, err = strWithTimeout.Write(bytes.Repeat([]byte{0}, 100))
|
|
Expect(err).To(MatchError("Write on stream 1337 canceled with error code 1234"))
|
|
close(writeReturned)
|
|
}()
|
|
var frame *wire.StreamFrame
|
|
Eventually(func() *wire.StreamFrame {
|
|
frame, _ = str.popStreamFrame(50)
|
|
return frame
|
|
}).ShouldNot(BeNil())
|
|
err := str.CancelWrite(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Eventually(writeReturned).Should(BeClosed())
|
|
Expect(n).To(BeEquivalentTo(frame.DataLen()))
|
|
})
|
|
|
|
It("cancels the context", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
Expect(str.Context().Done()).ToNot(BeClosed())
|
|
str.CancelWrite(1234)
|
|
Expect(str.Context().Done()).To(BeClosed())
|
|
})
|
|
|
|
It("doesn't allow further calls to Write", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
err := str.CancelWrite(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = strWithTimeout.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError("Write on stream 1337 canceled with error code 1234"))
|
|
})
|
|
|
|
It("only cancels once", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
err := str.CancelWrite(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.CancelWrite(4321)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("doesn't cancel when the stream was already closed", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
err := str.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.CancelWrite(123)
|
|
Expect(err).To(MatchError("CancelWrite for closed stream 1337"))
|
|
})
|
|
})
|
|
|
|
Context("receiving STOP_SENDING frames", func() {
|
|
It("queues a RST_STREAM frames with error code Stopping", func() {
|
|
mockSender.EXPECT().queueControlFrame(&wire.RstStreamFrame{
|
|
StreamID: streamID,
|
|
ErrorCode: errorCodeStopping,
|
|
})
|
|
str.handleStopSendingFrame(&wire.StopSendingFrame{
|
|
StreamID: streamID,
|
|
ErrorCode: 101,
|
|
})
|
|
})
|
|
|
|
It("unblocks Write", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := str.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError("Stream 1337 was reset with error code 123"))
|
|
Expect(err).To(BeAssignableToTypeOf(streamCanceledError{}))
|
|
Expect(err.(streamCanceledError).Canceled()).To(BeTrue())
|
|
Expect(err.(streamCanceledError).ErrorCode()).To(Equal(protocol.ApplicationErrorCode(123)))
|
|
close(done)
|
|
}()
|
|
Consistently(done).ShouldNot(BeClosed())
|
|
str.handleStopSendingFrame(&wire.StopSendingFrame{
|
|
StreamID: streamID,
|
|
ErrorCode: 123,
|
|
})
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("doesn't allow further calls to Write", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
str.handleStopSendingFrame(&wire.StopSendingFrame{
|
|
StreamID: streamID,
|
|
ErrorCode: 123,
|
|
})
|
|
_, err := str.Write([]byte("foobar"))
|
|
Expect(err).To(MatchError("Stream 1337 was reset with error code 123"))
|
|
Expect(err).To(BeAssignableToTypeOf(streamCanceledError{}))
|
|
Expect(err.(streamCanceledError).Canceled()).To(BeTrue())
|
|
Expect(err.(streamCanceledError).ErrorCode()).To(Equal(protocol.ApplicationErrorCode(123)))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("saying if it is finished", func() {
|
|
It("is finished after it is closed for shutdown", func() {
|
|
str.closeForShutdown(errors.New("testErr"))
|
|
Expect(str.finished()).To(BeTrue())
|
|
})
|
|
|
|
It("is finished after Close()", func() {
|
|
mockSender.EXPECT().onHasStreamData(streamID)
|
|
str.Close()
|
|
f, _ := str.popStreamFrame(1000)
|
|
Expect(f.FinBit).To(BeTrue())
|
|
Expect(str.finished()).To(BeTrue())
|
|
})
|
|
|
|
It("is finished after CancelWrite", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
err := str.CancelWrite(123)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(str.finished()).To(BeTrue())
|
|
})
|
|
|
|
It("is finished after receiving a STOP_SENDING (and sending a RST_STREAM)", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
str.handleStopSendingFrame(&wire.StopSendingFrame{StreamID: streamID})
|
|
Expect(str.finished()).To(BeTrue())
|
|
})
|
|
})
|
|
})
|