cardano-foundation / cardano-graphql

GraphQL API for Cardano
Apache License 2.0
260 stars 103 forks source link

Feature Request: Configurable socket timeout #183

Closed islishude closed 4 years ago

islishude commented 4 years ago

the server returns EOF errors very frequently.

Post "http://127.0.0.1:3100": EOF 

os: ubuntu 16.04 docker: Docker version 19.03.8, build afacb8b version: inputoutput/cardano-graphql:v1.0.0-rc.11 client: golang http client

I found a clumsy solution,but I don't want to close TCP connection every http request.

rhyslbw commented 4 years ago

Can you paste a sample of your client code where it's calling the API? Any additional logs would also be useful, as I'm not familiar with that particular client.

islishude commented 4 years ago

The example code

package main

import (
    "bytes"
    "encoding/json"
    "io"
    "net/http"
    "os"
    "time"
)

const endpoint = "http://10.60.82.101:3100/graphql"

const TipQuery = `query Tip {
    cardano {
      currentEpoch {
        blocks(limit: 1, order_by: { slotWithinEpoch: desc }) {
          hash
          epochNo
          slotWithinEpoch
          createdAt
          transactionsCount
          previousBlock {
            hash
            epochNo
            slotWithinEpoch
            createdAt
            transactionsCount
          }
        }
      }
    }
  }`

func main() {
    for i := 0; i < 100; i++ {
        Example()
        <-time.After(time.Second * 5)
    }
}

func Example() {
    reqdata := bytes.NewBuffer(nil)
    _ = json.NewEncoder(reqdata).Encode(map[string]string{"query": TipQuery})
    req, err := http.NewRequest(http.MethodPost, endpoint, reqdata)
    if err != nil {
        panic(err)
    }
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("Accept", "application/json")

    // close TCP connection when request is finished
        // and no EOF error returns
    // req.Close = true

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    _, _ = io.Copy(os.Stdout, resp.Body)
}

Log

{"data":{"cardano":{"currentEpoch":{"blocks":[{"hash":"472bd4050fec1619544513220c03695f9061a328f9009d1c04abac3064171811","epochNo":195,"slotWithinEpoch":15718,"createdAt":"2020-05-29T13:04:11","transactionsCount":"2","previousBlock":{"hash":"8520ab8e80f69f597a67209ed52a5e960caf77d1a4e864908c7b56514f629f90","epochNo":195,"slotWithinEpoch":15717,"createdAt":"2020-05-29T13:03:51","transactionsCount":"0"}}]}}}}
{"data":{"cardano":{"currentEpoch":{"blocks":[{"hash":"472bd4050fec1619544513220c03695f9061a328f9009d1c04abac3064171811","epochNo":195,"slotWithinEpoch":15718,"createdAt":"2020-05-29T13:04:11","transactionsCount":"2","previousBlock":{"hash":"8520ab8e80f69f597a67209ed52a5e960caf77d1a4e864908c7b56514f629f90","epochNo":195,"slotWithinEpoch":15717,"createdAt":"2020-05-29T13:03:51","transactionsCount":"0"}}]}}}}
panic: Post "http://10.60.82.101:3100/graphql": EOF

goroutine 1 [running]:
main.call()
        /project/go-wallet/cardano/tmp/main.go:55 +0x4f0
main.main()
        /project/go-wallet/cardano/tmp/main.go:38 +0x2b

And graphql server has no logs

graphql-api_1  | GraphQL HTTP server at http://localhost:3100/

hasura logs,but has no failed rquest log

hasura_1       | {"type":"query-log","timestamp":"2020-05-29T14:10:02.915+0000","level":"info","detail":{"request_id":"7f7c87f5-1444-43cc-9c5d-27bd4f60b24b","generated_sql":{"cardano":{"prepared_arguments":["{\"x-hasura-role\":\"cardano-graphql\"}"],"query":"SELECT  coalesce(json_agg(\"root\" ), '[]' ) AS \"root\" FROM  (SELECT  row_to_json((SELECT  \"_11_e\"  FROM  (SELECT  \"_10_root.or.currentEpoch\".\"currentEpoch\" AS \"currentEpoch\"       ) AS \"_11_e\"      ) ) AS \"root\" FROM  (SELECT  *  FROM \"public\".\"Cardano\"  WHERE ('true')     ) AS \"_0_root.base\" LEFT OUTER JOIN LATERAL (SELECT  row_to_json((SELECT  \"_9_e\"  FROM  (SELECT  \"_8_root.or.currentEpoch.ar.currentEpoch.blocks\".\"blocks\" AS \"blocks\"       ) AS \"_9_e\"      ) ) AS \"currentEpoch\" FROM  (SELECT  *  FROM \"public\".\"Epoch\"  WHERE ((\"_0_root.base\".\"currentEpochNo\") = (\"number\"))     ) AS \"_1_root.or.currentEpoch.base\" LEFT OUTER JOIN LATERAL (SELECT  coalesce(json_agg(\"blocks\" ORDER BY \"root.or.currentEpoch.ar.currentEpoch.blocks.pg.createdAt\" DESC NULLS FIRST), '[]' ) AS \"blocks\" FROM  (SELECT  row_to_json((SELECT  \"_6_e\"  FROM  (SELECT  \"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\".\"hash\" AS \"hash\", \"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\".\"epochNo\" AS \"epochNo\", \"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\".\"slotWithinEpoch\" AS \"slotWithinEpoch\", \"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\".\"createdAt\" AS \"createdAt\", \"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\".\"transactionsCount\" AS \"transactionsCount\", \"_5_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock\".\"previousBlock\" AS \"previousBlock\"       ) AS \"_6_e\"      ) ) AS \"blocks\", \"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\".\"createdAt\" AS \"root.or.currentEpoch.ar.currentEpoch.blocks.pg.createdAt\" FROM  (SELECT  *  FROM \"public\".\"Block\"  WHERE ((\"_1_root.or.currentEpoch.base\".\"number\") = (\"epochNo\"))     ) AS \"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\" LEFT OUTER JOIN LATERAL (SELECT  row_to_json((SELECT  \"_4_e\"  FROM  (SELECT  \"_3_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock.base\".\"hash\" AS \"hash\", \"_3_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock.base\".\"epochNo\" AS \"epochNo\", \"_3_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock.base\".\"slotWithinEpoch\" AS \"slotWithinEpoch\", \"_3_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock.base\".\"createdAt\" AS \"createdAt\", \"_3_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock.base\".\"transactionsCount\" AS \"transactionsCount\"       ) AS \"_4_e\"      ) ) AS \"previousBlock\" FROM  (SELECT  *  FROM \"public\".\"Block\"  WHERE ((\"_2_root.or.currentEpoch.ar.currentEpoch.blocks.base\".\"previousBlockHash\") = (\"hash\"))     ) AS \"_3_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock.base\"     LIMIT 100 ) AS \"_5_root.or.currentEpoch.ar.currentEpoch.blocks.or.previousBlock\" ON ('true')    ORDER BY \"root.or.currentEpoch.ar.currentEpoch.blocks.pg.createdAt\" DESC NULLS FIRST LIMIT 1 ) AS \"_7_root.or.currentEpoch.ar.currentEpoch.blocks\"      ) AS \"_8_root.or.currentEpoch.ar.currentEpoch.blocks\" ON ('true')     LIMIT 100 ) AS \"_10_root.or.currentEpoch\" ON ('true')     LIMIT 1 ) AS \"_12_root\"      "}},"query":{"variables":{},"query":"{\n  cardano {\n    currentEpoch {\n      blocks(limit: 1, order_by: {createdAt: desc}) {\n        hash\n        epochNo\n        slotWithinEpoch\n        createdAt\n        transactionsCount\n        previousBlock {\n          hash\n          epochNo\n          slotWithinEpoch\n          createdAt\n          transactionsCount\n        }\n      }\n    }\n  }\n}\n"}}}
hasura_1       | {"type":"http-log","timestamp":"2020-05-29T14:10:02.915+0000","level":"info","detail":{"operation":{"query_execution_time":0.375825908,"user_vars":{"x-hasura-role":"cardano-graphql"},"request_id":"7f7c87f5-1444-43cc-9c5d-27bd4f60b24b","response_size":266,"request_read_time":1.6171e-5},"http_info":{"status":200,"http_version":"HTTP/1.1","url":"/v1/graphql","ip":"172.25.0.7","method":"POST","content_encoding":"gzip"}}}
rhyslbw commented 4 years ago

@islishude Apologies for the delay in getting back to you on this. Might just be missing a header, try:

req.Header.Add("Connection", "keep-alive")
islishude commented 4 years ago

I tried it before,and at now still not works

rhyslbw commented 4 years ago

By default the socket will timeout after 2 minutes. I can make this configurable, however it's low priority at this stage.

rhyslbw commented 4 years ago

@islishude I've added the config option in https://github.com/input-output-hk/cardano-graphql/commit/5e337688a4d21f3d5ebe60f576ea7d2f890d433f. Can you see if this works in your application? I'll be adding tests, but this will give you a head-start

islishude commented 4 years ago

OK,I will test it later

islishude commented 4 years ago

still doesn't work

I try it with flowing code

const { ApolloServer, gql } = require("apollo-server");

const typeDefs = gql`
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

const books = [
  {
    title: "Harry Potter and the Chamber of Secrets",
    author: "J.K. Rowling",
  },
  {
    title: "Jurassic Park",
    author: "Michael Crichton",
  },
];

const resolvers = {
  Query: {
    books: () => books,
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
  server.httpServer.on("connection", (socket) => {
    socket.setTimeout(30 * 1000);
  });
  console.log(`🚀  Server ready at ${url}`);
});
islishude commented 4 years ago

close,I think I should use req.Close = true to resolve the bug.

this should be a problem with the nodejs http server or golang http client,I reproduced the bug in a simple echo server.

import { createServer } from "http";
import { on } from "events";

const reqs = on(createServer().listen(3000), "request");

console.log("serving...");
for await (const [req, res] of reqs) {
  req.pipe(res);
}
package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
    "time"
)

func main() {
    for i := 0; i < 100; i++ {
        Example()
        <-time.After(time.Second * 5)
    }
}

func Example() {
    const endpoint = "http://localhost:3000/graphql"
    req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader("test\n"))
    if err != nil {
        panic(err)
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
    _, _ = io.Copy(os.Stdout, resp.Body)
}

output

test
test
Post "http://localhost:3000/graphql": EOF
test
Post "http://localhost:3000/graphql": EOF
test
Post "http://localhost:3000/graphql": EOF
test
Post "http://localhost:3000/graphql": EOF
test
Post "http://localhost:3000/graphql": EOF
test
Post "http://localhost:3000/graphql": EOF
test