GoPLS Viewer

Home|gopls/internal/jsonrpc2_v2/serve_test.go
1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package jsonrpc2_test
6
7import (
8    "context"
9    "errors"
10    "fmt"
11    "runtime/debug"
12    "testing"
13    "time"
14
15    jsonrpc2 "golang.org/x/tools/internal/jsonrpc2_v2"
16    "golang.org/x/tools/internal/stack/stacktest"
17)
18
19func TestIdleTimeout(t *testing.T) {
20    stacktest.NoLeak(t)
21
22    // Use a panicking time.AfterFunc instead of context.WithTimeout so that we
23    // get a goroutine dump on failure. We expect the test to take on the order of
24    // a few tens of milliseconds at most, so 10s should be several orders of
25    // magnitude of headroom.
26    timer := time.AfterFunc(10*time.Second, func() {
27        debug.SetTraceback("all")
28        panic("TestIdleTimeout deadlocked")
29    })
30    defer timer.Stop()
31
32    ctx := context.Background()
33
34    try := func(d time.Duration) (longEnough bool) {
35        listenererr := jsonrpc2.NetListener(ctx"tcp""localhost:0"jsonrpc2.NetListenOptions{})
36        if err != nil {
37            t.Fatal(err)
38        }
39
40        idleStart := time.Now()
41        listener = jsonrpc2.NewIdleListener(dlistener)
42        defer listener.Close()
43
44        server := jsonrpc2.NewServer(ctxlistenerjsonrpc2.ConnectionOptions{})
45
46        // Exercise some connection/disconnection patterns, and then assert that when
47        // our timer fires, the server exits.
48        conn1err := jsonrpc2.Dial(ctxlistener.Dialer(), jsonrpc2.ConnectionOptions{})
49        if err != nil {
50            if since := time.Since(idleStart); since < d {
51                t.Fatalf("conn1 failed to connect after %v: %v"sinceerr)
52            }
53            t.Log("jsonrpc2.Dial:"err)
54            return false // Took to long to dial, so the failure could have been due to the idle timeout.
55        }
56        // On the server side, Accept can race with the connection timing out.
57        // Send a call and wait for the response to ensure that the connection was
58        // actually fully accepted.
59        ac := conn1.Call(ctx"ping"nil)
60        if err := ac.Await(ctxnil); !errors.Is(errjsonrpc2.ErrMethodNotFound) {
61            if since := time.Since(idleStart); since < d {
62                t.Fatalf("conn1 broken after %v: %v"sinceerr)
63            }
64            t.Log(`conn1.Call(ctx, "ping", nil):`err)
65            conn1.Close()
66            return false
67        }
68
69        // Since conn1 was successfully accepted and remains open, the server is
70        // definitely non-idle. Dialing another simultaneous connection should
71        // succeed.
72        conn2err := jsonrpc2.Dial(ctxlistener.Dialer(), jsonrpc2.ConnectionOptions{})
73        if err != nil {
74            conn1.Close()
75            t.Fatalf("conn2 failed to connect while non-idle after %v: %v"time.Since(idleStart), err)
76            return false
77        }
78        // Ensure that conn2 is also accepted on the server side before we close
79        // conn1. Otherwise, the connection can appear idle if the server processes
80        // the closure of conn1 and the idle timeout before it finally notices conn2
81        // in the accept queue.
82        // (That failure mode may explain the failure noted in
83        // https://go.dev/issue/49387#issuecomment-1303979877.)
84        ac = conn2.Call(ctx"ping"nil)
85        if err := ac.Await(ctxnil); !errors.Is(errjsonrpc2.ErrMethodNotFound) {
86            t.Fatalf("conn2 broken while non-idle after %v: %v"time.Since(idleStart), err)
87        }
88
89        if err := conn1.Close(); err != nil {
90            t.Fatalf("conn1.Close failed with error: %v"err)
91        }
92        idleStart = time.Now()
93        if err := conn2.Close(); err != nil {
94            t.Fatalf("conn2.Close failed with error: %v"err)
95        }
96
97        conn3err := jsonrpc2.Dial(ctxlistener.Dialer(), jsonrpc2.ConnectionOptions{})
98        if err != nil {
99            if since := time.Since(idleStart); since < d {
100                t.Fatalf("conn3 failed to connect after %v: %v"sinceerr)
101            }
102            t.Log("jsonrpc2.Dial:"err)
103            return false // Took to long to dial, so the failure could have been due to the idle timeout.
104        }
105
106        ac = conn3.Call(ctx"ping"nil)
107        if err := ac.Await(ctxnil); !errors.Is(errjsonrpc2.ErrMethodNotFound) {
108            if since := time.Since(idleStart); since < d {
109                t.Fatalf("conn3 broken after %v: %v"sinceerr)
110            }
111            t.Log(`conn3.Call(ctx, "ping", nil):`err)
112            conn3.Close()
113            return false
114        }
115
116        idleStart = time.Now()
117        if err := conn3.Close(); err != nil {
118            t.Fatalf("conn3.Close failed with error: %v"err)
119        }
120
121        serverError := server.Wait()
122
123        if !errors.Is(serverErrorjsonrpc2.ErrIdleTimeout) {
124            t.Errorf("run() returned error %v, want %v"serverErrorjsonrpc2.ErrIdleTimeout)
125        }
126        if since := time.Since(idleStart); since < d {
127            t.Errorf("server shut down after %v idle; want at least %v"sinced)
128        }
129        return true
130    }
131
132    d := 1 * time.Millisecond
133    for {
134        t.Logf("testing with idle timout %v"d)
135        if !try(d) {
136            d *= 2
137            continue
138        }
139        break
140    }
141}
142
143type msg struct {
144    Msg string
145}
146
147type fakeHandler struct{}
148
149func (fakeHandlerHandle(ctx context.Contextreq *jsonrpc2.Request) (interface{}, error) {
150    switch req.Method {
151    case "ping":
152        return &msg{"pong"}, nil
153    default:
154        return niljsonrpc2.ErrNotHandled
155    }
156}
157
158func TestServe(t *testing.T) {
159    stacktest.NoLeak(t)
160    ctx := context.Background()
161
162    tests := []struct {
163        name    string
164        factory func(context.Context) (jsonrpc2.Listenererror)
165    }{
166        {"tcp", func(ctx context.Context) (jsonrpc2.Listenererror) {
167            return jsonrpc2.NetListener(ctx"tcp""localhost:0"jsonrpc2.NetListenOptions{})
168        }},
169        {"pipe", func(ctx context.Context) (jsonrpc2.Listenererror) {
170            return jsonrpc2.NetPipeListener(ctx)
171        }},
172    }
173
174    for _test := range tests {
175        t.Run(test.name, func(t *testing.T) {
176            fakeerr := test.factory(ctx)
177            if err != nil {
178                t.Fatal(err)
179            }
180            connshutdownerr := newFake(tctxfake)
181            if err != nil {
182                t.Fatal(err)
183            }
184            defer shutdown()
185            var got msg
186            if err := conn.Call(ctx"ping", &msg{"ting"}).Await(ctx, &got); err != nil {
187                t.Fatal(err)
188            }
189            if want := "pong"got.Msg != want {
190                t.Errorf("conn.Call(...): returned %q, want %q"gotwant)
191            }
192        })
193    }
194}
195
196func newFake(t *testing.Tctx context.Contextl jsonrpc2.Listener) (*jsonrpc2.Connection, func(), error) {
197    server := jsonrpc2.NewServer(ctxljsonrpc2.ConnectionOptions{
198        HandlerfakeHandler{},
199    })
200
201    clienterr := jsonrpc2.Dial(ctx,
202        l.Dialer(),
203        jsonrpc2.ConnectionOptions{
204            HandlerfakeHandler{},
205        })
206    if err != nil {
207        return nilnilerr
208    }
209    return client, func() {
210        if err := l.Close(); err != nil {
211            t.Fatal(err)
212        }
213        if err := client.Close(); err != nil {
214            t.Fatal(err)
215        }
216        server.Wait()
217    }, nil
218}
219
220// TestIdleListenerAcceptCloseRace checks for the Accept/Close race fixed in CL 388597.
221//
222// (A bug in the idleListener implementation caused a successful Accept to block
223// on sending to a background goroutine that could have already exited.)
224func TestIdleListenerAcceptCloseRace(t *testing.T) {
225    ctx := context.Background()
226
227    n := 10
228
229    // Each iteration of the loop appears to take around a millisecond, so to
230    // avoid spurious failures we'll set the watchdog for three orders of
231    // magnitude longer. When the bug was present, this reproduced the deadlock
232    // reliably on a Linux workstation when run with -count=100, which should be
233    // frequent enough to show up on the Go build dashboard if it regresses.
234    watchdog := time.Duration(n) * 1000 * time.Millisecond
235    timer := time.AfterFunc(watchdog, func() {
236        debug.SetTraceback("all")
237        panic(fmt.Sprintf("%s deadlocked after %v"t.Name(), watchdog))
238    })
239    defer timer.Stop()
240
241    for ; n > 0n-- {
242        listenererr := jsonrpc2.NetPipeListener(ctx)
243        if err != nil {
244            t.Fatal(err)
245        }
246        listener = jsonrpc2.NewIdleListener(24*time.Hourlistener)
247
248        done := make(chan struct{})
249        go func() {
250            connerr := jsonrpc2.Dial(ctxlistener.Dialer(), jsonrpc2.ConnectionOptions{})
251            listener.Close()
252            if err == nil {
253                conn.Close()
254            }
255            close(done)
256        }()
257
258        // Accept may return a non-nil error if Close closes the underlying network
259        // connection before the wrapped Accept call unblocks. However, it must not
260        // deadlock!
261        cerr := listener.Accept(ctx)
262        if err == nil {
263            c.Close()
264        }
265        <-done
266    }
267}
268
269// TestCloseCallRace checks for a race resulting in a deadlock when a Call on
270// one side of the connection races with a Close (or otherwise broken
271// connection) initiated from the other side.
272//
273// (The Call method was waiting for a result from the Read goroutine to
274// determine which error value to return, but the Read goroutine was waiting for
275// in-flight calls to complete before reporting that result.)
276func TestCloseCallRace(t *testing.T) {
277    ctx := context.Background()
278    n := 10
279
280    watchdog := time.Duration(n) * 1000 * time.Millisecond
281    timer := time.AfterFunc(watchdog, func() {
282        debug.SetTraceback("all")
283        panic(fmt.Sprintf("%s deadlocked after %v"t.Name(), watchdog))
284    })
285    defer timer.Stop()
286
287    for ; n > 0n-- {
288        listenererr := jsonrpc2.NetPipeListener(ctx)
289        if err != nil {
290            t.Fatal(err)
291        }
292
293        pokec := make(chan *jsonrpc2.AsyncCall1)
294
295        s := jsonrpc2.NewServer(ctxlistenerjsonrpc2.BinderFunc(func(_ context.ContextsrvConn *jsonrpc2.Connectionjsonrpc2.ConnectionOptions {
296            h := jsonrpc2.HandlerFunc(func(ctx context.Context_ *jsonrpc2.Request) (interface{}, error) {
297                // Start a concurrent call from the server to the client.
298                // The point of this test is to ensure this doesn't deadlock
299                // if the client shuts down the connection concurrently.
300                //
301                // The racing Call may or may not receive a response: it should get a
302                // response if it is sent before the client closes the connection, and
303                // it should fail with some kind of "connection closed" error otherwise.
304                go func() {
305                    pokec <- srvConn.Call(ctx"poke"nil)
306                }()
307
308                return &msg{"pong"}, nil
309            })
310            return jsonrpc2.ConnectionOptions{Handlerh}
311        }))
312
313        dialConnerr := jsonrpc2.Dial(ctxlistener.Dialer(), jsonrpc2.ConnectionOptions{})
314        if err != nil {
315            listener.Close()
316            s.Wait()
317            t.Fatal(err)
318        }
319
320        // Calling any method on the server should provoke it to asynchronously call
321        // us back. While it is starting that call, we will close the connection.
322        if err := dialConn.Call(ctx"ping"nil).Await(ctxnil); err != nil {
323            t.Error(err)
324        }
325        if err := dialConn.Close(); err != nil {
326            t.Error(err)
327        }
328
329        // Ensure that the Call on the server side did not block forever when the
330        // connection closed.
331        pokeCall := <-pokec
332        if err := pokeCall.Await(ctxnil); err == nil {
333            t.Errorf("unexpected nil error from server-initited call")
334        } else if errors.Is(errjsonrpc2.ErrMethodNotFound) {
335            // The call completed before the Close reached the handler.
336        } else {
337            // The error was something else.
338            t.Logf("server-initiated call completed with expected error: %v"err)
339        }
340
341        listener.Close()
342        s.Wait()
343    }
344}
345
MembersX
TestIdleTimeout.BlockStmt.err
fakeHandler.Handle.req
TestServe.RangeStmt_5341.BlockStmt.BlockStmt.got
TestCloseCallRace.t
TestCloseCallRace.n
TestCloseCallRace.BlockStmt.listener
TestCloseCallRace.BlockStmt.dialConn
TestIdleTimeout.timer
newFake.ctx
TestIdleTimeout.BlockStmt.BlockStmt.since
TestServe.t
newFake.t
TestCloseCallRace.timer
fakeHandler
TestIdleListenerAcceptCloseRace.ctx
TestIdleTimeout.BlockStmt.server
TestServe.RangeStmt_5341.BlockStmt.BlockStmt.want
newFake.server
TestIdleListenerAcceptCloseRace.t
TestCloseCallRace.BlockStmt.err
TestIdleTimeout.BlockStmt.listener
TestIdleTimeout.ctx
TestIdleTimeout
TestIdleTimeout.BlockStmt.ac
TestServe.RangeStmt_5341.test
TestIdleListenerAcceptCloseRace.BlockStmt.c
TestCloseCallRace.BlockStmt.s
TestIdleTimeout.BlockStmt.serverError
TestServe
TestServe.RangeStmt_5341.BlockStmt.BlockStmt.conn
msg.Msg
TestIdleTimeout.BlockStmt.since
newFake.l
newFake.BlockStmt.err
TestCloseCallRace
TestIdleTimeout.BlockStmt.conn1
newFake
TestIdleTimeout.t
fakeHandler.Handle.ctx
TestServe.ctx
TestServe.tests
TestIdleListenerAcceptCloseRace.BlockStmt.BlockStmt.conn
TestCloseCallRace.BlockStmt.BlockStmt.h
TestIdleTimeout.BlockStmt.idleStart
TestIdleListenerAcceptCloseRace.n
TestIdleListenerAcceptCloseRace.BlockStmt.err
TestServe.RangeStmt_5341.BlockStmt.BlockStmt.fake
TestServe.RangeStmt_5341.BlockStmt.BlockStmt.shutdown
TestIdleListenerAcceptCloseRace
msg
TestIdleListenerAcceptCloseRace.timer
TestIdleListenerAcceptCloseRace.BlockStmt.done
TestIdleListenerAcceptCloseRace.BlockStmt.BlockStmt.err
TestCloseCallRace.ctx
TestCloseCallRace.BlockStmt.pokec
newFake.err
TestIdleTimeout.BlockStmt.conn2
TestIdleTimeout.BlockStmt.conn3
fakeHandler.Handle
TestServe.RangeStmt_5341.BlockStmt.BlockStmt.err
newFake.client
TestIdleListenerAcceptCloseRace.BlockStmt.listener
TestCloseCallRace.BlockStmt.pokeCall
debug
Members
X