11package gbn
22
33import (
4+ "sync"
45 "testing"
6+ "time"
57
8+ "github.com/lightningnetwork/lnd/lntest/wait"
69 "github.com/stretchr/testify/require"
710)
811
@@ -19,3 +22,131 @@ func TestQueueSize(t *testing.T) {
1922 q .sequenceTop = 2
2023 require .Equal (t , uint8 (3 ), q .size ())
2124}
25+
26+ // TestQueueResend tests that the queue resend functionality works as expected.
27+ // It specifically tests that we actually resend packets, and await the expected
28+ // durations for cases when we resend and 1) don't receive the expected
29+ // ACK/NACK, 2) receive the expected ACK, and 3) receive the expected NACK.
30+ func TestQueueResend (t * testing.T ) {
31+ t .Parallel ()
32+
33+ resentPackets := make (map [uint8 ]struct {})
34+ queueTimeout := time .Second * 1
35+
36+ cfg := & queueCfg {
37+ s : 5 ,
38+ timeout : queueTimeout ,
39+ sendPkt : func (packet * PacketData ) error {
40+ resentPackets [packet .Seq ] = struct {}{}
41+
42+ return nil
43+ },
44+ }
45+ q := newQueue (cfg )
46+
47+ pkt1 := & PacketData {Seq : 1 }
48+ pkt2 := & PacketData {Seq : 2 }
49+ pkt3 := & PacketData {Seq : 3 }
50+
51+ q .addPacket (pkt1 )
52+
53+ // First test that we shouldn't resend if the timeout hasn't passed.
54+ q .lastResend = time .Now ()
55+
56+ err := q .resend ()
57+ require .NoError (t , err )
58+
59+ require .Empty (t , resentPackets )
60+
61+ // Secondly, let's test that we do resend if the timeout has passed, and
62+ // that we then start a sync once we've resent the packet.
63+ q .lastResend = time .Now ().Add (- queueTimeout * 2 )
64+
65+ // Let's first test the syncing scenario where we don't receive
66+ // the expected ACK/NACK for the resent packet. This should trigger a
67+ // timeout of the syncing, which should be the
68+ // queueTimeout * awaitingTimeoutMultiplier.
69+ startTime := time .Now ()
70+
71+ var wg sync.WaitGroup
72+ resend (t , q , & wg )
73+
74+ wg .Wait ()
75+
76+ // Check that the resend took at least the
77+ // queueTimeout * awaitingTimeoutMultiplier for the syncing to
78+ // complete, and that we actually resent the packet.
79+ require .GreaterOrEqual (
80+ t , time .Since (startTime ),
81+ queueTimeout * awaitingTimeoutMultiplier ,
82+ )
83+ require .Contains (t , resentPackets , pkt1 .Seq )
84+
85+ // Now let's test the syncing scenario where we do receive the
86+ // expected ACK for the resent packet. This should trigger a
87+ // queue.proceedAfterTime call, which should finish the syncing
88+ // after the queueTimeout.
89+ q .lastResend = time .Now ().Add (- queueTimeout * 2 )
90+
91+ q .addPacket (pkt2 )
92+
93+ startTime = time .Now ()
94+
95+ resend (t , q , & wg )
96+
97+ // Simulate that we receive the expected ACK for the resent packet.
98+ q .processACK (pkt2 .Seq )
99+
100+ wg .Wait ()
101+
102+ // Now check that the resend took at least the queueTimeout for the
103+ // syncing to complete, and that we actually resent the packet.
104+ require .GreaterOrEqual (t , time .Since (startTime ), queueTimeout )
105+ require .LessOrEqual (t , time .Since (startTime ), queueTimeout * 2 )
106+ require .Contains (t , resentPackets , pkt2 .Seq )
107+
108+ // Finally, let's test the syncing scenario where we do receive the
109+ // expected NACK for the resent packet. This make the syncing
110+ // complete immediately.
111+ q .lastResend = time .Now ().Add (- queueTimeout * 2 )
112+
113+ q .addPacket (pkt3 )
114+
115+ startTime = time .Now ()
116+ resend (t , q , & wg )
117+
118+ // Simulate that we receive the expected NACK for the resent packet.
119+ q .processNACK (pkt3 .Seq + 1 )
120+
121+ wg .Wait ()
122+
123+ // Finally let's check that we didn't await any timeout now, and that
124+ // we actually resent the packet.
125+ require .Less (t , time .Since (startTime ), queueTimeout )
126+ require .Contains (t , resentPackets , pkt3 .Seq )
127+ }
128+
129+ // resend is a helper function that resends packets in a goroutine, and notifies
130+ // the WaitGroup when the resend + syncing has completed.
131+ // The function will block until the resend has actually started.
132+ func resend (t * testing.T , q * queue , wg * sync.WaitGroup ) {
133+ t .Helper ()
134+
135+ wg .Add (1 )
136+
137+ // This will trigger a sync, so we launch the resend in a
138+ // goroutine.
139+ go func () {
140+ err := q .resend ()
141+ require .NoError (t , err )
142+
143+ wg .Done ()
144+ }()
145+
146+ // We also ensure that the above goroutine is has started the resend
147+ // before this function returns.
148+ err := wait .Predicate (func () bool {
149+ return q .syncer .getState () == syncStateResending
150+ }, time .Second )
151+ require .NoError (t , err )
152+ }
0 commit comments