Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unknown response causing a permanent transport failure in multiplexing client #28

Closed
estaban opened this issue Mar 17, 2022 · 4 comments

Comments

@estaban
Copy link
Contributor

estaban commented Mar 17, 2022

When the multiplexing client receives an unknown response from the network, it causes a permanent transport failure (Error::Desynchronized).

This scenario can occur for example when the server replies after the client-side timeout.

In my case, I am using tokio-tower to implement a client over a UDP-based multiplexed protocol. My client sends two requests to two random servers, and waits for the first one to reply. After the first reply is received, the inflight request of the second connection is cleaned up from the tag store (using RAII). However, when the reply from the second server is received, it is unknown to the tag store, and the transport fails with Error::Desynchronized.

Could we please provide an option to the user to ignore received responses which do not match any inflight requests?

@estaban
Copy link
Contributor Author

estaban commented Mar 22, 2022

Here is a suggestion to modify the TagStore trait that would solve this issue, along with a few others:

  • The TagStore is actually not a store, it does not contain the inflight requests. Inflight requests are tracked in a list internal to tokio-tower.
  • The inflight request lookup time is linear in the number of inflight requests, which could be problenatic for applications with a large amount of inflight requests. It would be great to defer the choice of the internal data structure to the user.
  • In my application, I want to measure the "network time", ie the time from when the request was written to the socket, to when the response was read from the socket. To do this, when receive a response, I need to retrieve some timestamps recorded when serializing the request. I could have another hashmap to map the request id to my timestamps, but this makes the code more complex, as two states need to be maintained now (this hashmap + the internal linked list). Giving the user access to the looked up request from the tag store would solve this problem with a single data store and a single lookup.
  • In order to prevent server timeouts from leaking inflight requests in the tag store, the user needs to implement their own Sink/Stream to send ghost responses. This is a lot of boilerplate complex code. It would be great if the user could mutate the tag store directly to remove inflight requests.

The TagStore trait could actually store the inflight requests, and the user could implement the lookup the way they want:

pub trait TagStore<Request, Response> {
    /// The type used for tags.
    type Tag;

    /// Assign a fresh tag to the given `Request`, and return that tag.
    fn assign_tag(self: Pin<&mut Self>, r: &mut Request) -> Self::Tag;

    /// Insert an inflight request in the store
    fn add_inflight(self: Pin<&mut Self>, tag: Self::Tag, inflight: Pending<Self::Tag, Response>);

    /// Look up and remove the inflight request matching the given `Response`
    /// * Returns Ok(x) if an inflight request has been found to match the given `Response`
    /// * Returns Err(False) if no request was found, and this is a permanent error
    /// * Returns Err(true) if no request was found, and this is not a permanent error
    fn remove_inflight(self: Pin<&mut Self>, r: &Response) -> Result<Pending<Self::Tag, Response>, bool>;
}

@estaban
Copy link
Contributor Author

estaban commented Mar 22, 2022

@LucioFranco

@LucioFranco
Copy link
Member

#29 tries to cover both use cases

@LucioFranco
Copy link
Member

@estaban we can close this right?

@estaban estaban closed this as completed Sep 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants