Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

HttpResponseTooShort for first request after a while #79

Open
jorian opened this issue Dec 7, 2022 · 11 comments
Open

HttpResponseTooShort for first request after a while #79

jorian opened this issue Dec 7, 2022 · 11 comments

Comments

@jorian
Copy link

jorian commented Dec 7, 2022

I have this strange behaviour with a Discord bot that I'm currently making.

For a cryptocurrency project, I am using rust-jsonrpc as a client to query that cryptocurrency's blockchain. It uses the same RPC mechanism that Bitcoin uses, so I'm using this crate and it has been working fine for quite some time.

Because I have to do many RPCs, I store an instance of a rust-jsonrpc client as global data of the bot. This global data is essentially a Box::pin(async move { Data } ) where I store the client as a member of Data. (the bot is made using poise, and in this setup function is where Data gets initiated)

When I start the bot and keep it running for a while (30+ sec) and get the client from global data and do a getnewaddress RPC, I am getting a HttpResponseTooShort error: error: JsonRPC(Transport(HttpResponseTooShort { actual: 0, needed: 12 })). It happens every time I use this call after at least 30 seconds of idling and I can't figure out why this happens. A second request within 30 seconds works just fine.

I've already checked if the problem is keeping a client around for a while:

    let client = vrsc_rpc::Client::vrsc(true, Auth::ConfigFile).unwrap();

    thread::sleep(Duration::from_secs(50));

    dbg!(client.get_new_address().unwrap());

(I built a wrapper like https://github.com/rust-bitcoin/rust-bitcoincore-rpc for this cryptocurrency project)

This doesn't appear to be a problem. I can't figure out what is, though. My guess would be that I keep a connection open for too long, but I can't figure this out, as it appears to be working in a separate environment.

Any ideas?

@dpc
Copy link
Contributor

dpc commented Dec 7, 2022

Sounds like a connection reuse issue after timeout.

#80 possibly related?

@apoelstra
Copy link
Owner

I agree, it sounds like we're failing to detect that the socket has died. When we do the POST request we should be detecting errors (on the second write) and retrying.

From http://www.softlab.ntua.gr/facilities/documentation/unix/unix-socket-faq/unix-socket-faq-2.html

"If the peer calls close() or exits...I would expect EPIPE, not on the next call, but the one after."

@jorian
Copy link
Author

jorian commented Dec 12, 2022

I'm glad it looks like the issue is known. What would be the timeline for a fix?

@apoelstra
Copy link
Owner

This week.

@jorian
Copy link
Author

jorian commented Jan 23, 2023

What is the status on this?

@apoelstra
Copy link
Owner

No update. I've had it as an open tab for 8 weeks. It's near the top of my TODO list. Sorry.

@apoelstra
Copy link
Owner

@jorian opened #84 as a fix.

@jorian
Copy link
Author

jorian commented Jan 25, 2023

Thanks! No need to say sorry :D

@jorian
Copy link
Author

jorian commented Jan 30, 2023

I checked out the branch that this PR uses and use it in my Cargo.toml:

jsonrpc = {git="https://github.com/apoelstra/rust-jsonrpc", branch = "2023-01--detect-epipe", version = "0.14.0"} 

Unfortunately, I still get the error message: RPC error: transport error: HTTP response too short: length 0, needed 12.

EDIT: I just noticed it got merged to master branch, but it seems to be needing some more investigation.

@apoelstra
Copy link
Owner

Damn. We haven't merged it yet (but we will, since I've run into the issue that it fixes). Frustrating that it doesn't fix your issue.

Are you able to use tcpdump or something to get more information?

@jorian
Copy link
Author

jorian commented Jan 30, 2023

Well I decided to just stop reusing clients for now as there is no requirement for me to do so.

I don't have the skills to use tcpdump to find out more, sorry.

apoelstra added a commit that referenced this issue Jan 30, 2023
…he socket

5b7b23c simple_http: if writing fails, try reconnecting the socket (once) (Andrew Poelstra)
1d11885 simple_http: rename writeable version of `sock` to `write_sock` (Andrew Poelstra)
cfc4e6f simple_http: abstract socket recreation into `fresh_socket` method (Andrew Poelstra)

Pull request description:

  Attempt at #79. Does not fix the issue, but will merge this anyway since it fixes an issue I've hit in other projects.

ACKs for top commit:
  tcharding:
    ACK 5b7b23c

Tree-SHA512: ab1384463f9d5493d2cb47ed1b09775d6e89db702e5ab1e8c18d99f264bb3c140022b4a8402db664e68a082ef32dcaf498abb96f0a9a27892e67b21a5bb6cef8
apoelstra added a commit that referenced this issue Mar 20, 2023
0de1c58 simple_http: unit test to demonstrate detecting broken pipe (Philip Robinson)
0cab08a simple_http: add second send attempt if read indicates socket is broken (Philip Robinson)

Pull request description:

  While upgrading from v0.11 to v0.14 I noticed that I started getting the following error when the server had disconnected the socket between requests, in our case due to the server idle timeout:
  ```
  Err(Transport(HttpResponseTooShort { actual: 0, needed: 12 }))
  ```
  The same issue as is reported in #79 I believe.

  I noticed #84 was an attempt to solve the problem and I think I found out why it didn't work as expected.

  The client is using a `BufWriter` to write the `TcpStream` and unless you flush the buffer you will not see the broken pipe error in time in order to request a fresh connection.

  This PR adds in the flushes and provides a unit test demonstrating that it fixes the issue. If you comment out the flushes you will see the symptom reported in #79.

  ## Revision
  The first approach of flushing the write buffer twice during the POST angered HTTP servers that expected the request to be contained in a single message.

  The revised approach uses the method that is advocated in the unix docs to detect a broken TCP stream which is that if the blocking `read` operation returns 0 bytes then the socket is closed. So this PR creates a buffer to hold the request, attempts to send it and then reads the response. If the response was length 0 it will attempt to get a fresh socket and send the request a second time.

ACKs for top commit:
  apoelstra:
    ACK 0de1c58

Tree-SHA512: 0871a0c57b3d63b2d7b0ebd4bb1bb2f4f68bb88b7fe55fa8f3566f49fbd3aa33920e7e953e50320797bf2b1791ea223e6ef40f57901fbc3908a005e3ea167f77
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants