(f *http2.GoAwayFrame)
| 1359 | } |
| 1360 | |
| 1361 | func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) error { |
| 1362 | t.mu.Lock() |
| 1363 | if t.state == closing { |
| 1364 | t.mu.Unlock() |
| 1365 | return nil |
| 1366 | } |
| 1367 | if f.ErrCode == http2.ErrCodeEnhanceYourCalm && string(f.DebugData()) == "too_many_pings" { |
| 1368 | // When a client receives a GOAWAY with error code ENHANCE_YOUR_CALM and debug |
| 1369 | // data equal to ASCII "too_many_pings", it should log the occurrence at a log level that is |
| 1370 | // enabled by default and double the configure KEEPALIVE_TIME used for new connections |
| 1371 | // on that channel. |
| 1372 | logger.Errorf("Client received GoAway with error code ENHANCE_YOUR_CALM and debug data equal to ASCII \"too_many_pings\".") |
| 1373 | } |
| 1374 | id := f.LastStreamID |
| 1375 | if id > 0 && id%2 == 0 { |
| 1376 | t.mu.Unlock() |
| 1377 | return connectionErrorf(true, nil, "received goaway with non-zero even-numbered stream id: %v", id) |
| 1378 | } |
| 1379 | // A client can receive multiple GoAways from the server (see |
| 1380 | // https://github.com/grpc/grpc-go/issues/1387). The idea is that the first |
| 1381 | // GoAway will be sent with an ID of MaxInt32 and the second GoAway will be |
| 1382 | // sent after an RTT delay with the ID of the last stream the server will |
| 1383 | // process. |
| 1384 | // |
| 1385 | // Therefore, when we get the first GoAway we don't necessarily close any |
| 1386 | // streams. While in case of second GoAway we close all streams created after |
| 1387 | // the GoAwayId. This way streams that were in-flight while the GoAway from |
| 1388 | // server was being sent don't get killed. |
| 1389 | select { |
| 1390 | case <-t.goAway: // t.goAway has been closed (i.e.,multiple GoAways). |
| 1391 | // If there are multiple GoAways the first one should always have an ID greater than the following ones. |
| 1392 | if id > t.prevGoAwayID { |
| 1393 | t.mu.Unlock() |
| 1394 | return connectionErrorf(true, nil, "received goaway with stream id: %v, which exceeds stream id of previous goaway: %v", id, t.prevGoAwayID) |
| 1395 | } |
| 1396 | default: |
| 1397 | t.setGoAwayReason(f) |
| 1398 | close(t.goAway) |
| 1399 | defer t.controlBuf.put(&incomingGoAway{}) // Defer as t.mu is currently held. |
| 1400 | // Notify the clientconn about the GOAWAY before we set the state to |
| 1401 | // draining, to allow the client to stop attempting to create streams |
| 1402 | // before disallowing new streams on this connection. |
| 1403 | if t.state != draining { |
| 1404 | t.onClose(GoAwayInfo{Reason: t.goAwayReason, GoAwayCode: t.goAwayCode}) |
| 1405 | t.state = draining |
| 1406 | } |
| 1407 | } |
| 1408 | // All streams with IDs greater than the GoAwayId |
| 1409 | // and smaller than the previous GoAway ID should be killed. |
| 1410 | upperLimit := t.prevGoAwayID |
| 1411 | if upperLimit == 0 { // This is the first GoAway Frame. |
| 1412 | upperLimit = math.MaxUint32 // Kill all streams after the GoAway ID. |
| 1413 | } |
| 1414 | |
| 1415 | t.prevGoAwayID = id |
| 1416 | if len(t.activeStreams) == 0 { |
| 1417 | t.mu.Unlock() |
| 1418 | return connectionErrorf(true, nil, "received goaway and there are no active streams") |
no test coverage detected