GithubHelp home page GithubHelp logo

Comments (8)

gftea avatar gftea commented on May 29, 2024 1

summary to close this ticket

  1. I would prefer to keep the feature that try best effort to close connection and channel at drop, so keeping drop guard design
  2. the runtime exit case is not considered a use case needed to be handled in the library

from amqprs.

gftea avatar gftea commented on May 29, 2024

the master value is to control the drop behavior, what is your use case?

my idea was there is one and only one master connection, you can move the connection to another task/thread, it is not recommended to use multiple copy of same connection or channel in a concurrent context because it will cause out of order framing in network io. This is similar limitation in Java client, see limitation

from amqprs.

gftea avatar gftea commented on May 29, 2024

@McPatate you can try v0.9.1, which includes #31

from amqprs.

McPatate avatar McPatate commented on May 29, 2024

Ok! I've looked more into this issue, and this is what I think should be done to improve the Drop of the Connection object

move any reference of Connection out of WriterHandler and ReaderHandler

The main problem with cleanly dropping the connection is that the WriterHandler & the ReaderHandler are both run in background with tokio::spawn and each of them hold a clone of the original Connection.
When the user's Connection object goes out of scope without calling .close(), a reference will live on for as long as the background tasks are running - not what we want.

Given there is no AsyncDrop in rust yet, one must spawn a task in the impl Drop were they to run async code in drop(). This is also unreliable since you do not control when the execution ended; the task might even be discarded before it was run.

let ChannelDispatcher handle all frames

I think what should be done is remove any reference of Connection in both handlers and let the ChannelDispatcher handle every frame. The dispatcher has a reference of the Channel which also holds a reference to the Connection. It should thus be able to change the state of the connection (open/closed) and call the appropriate callbacks.

Channel is not thread-safe, so the user must explicitly call .close() once it has finished. If the user does not forget to call .close(), this means that whenever the last user-held Connection is dropped, everything will gracefully clean itself up:

  • Connection's tx will be dropped, making the handlers' rx.recv() return None and also cleaning up
  • ReaderHandler ChannelManager will be dropped, dropping every dispatcher_tx stopping every dispatcher.spawn background task.

remove the DropGuard

You do not need the DropGuard anymore, unless we want to make it safer for the user in case they forget to call .close() on the Channel, but that's up to you.

Also, if you need to print the Connection info/id in logs (like in the WriterHandler), I think you should only add a given field for the info to the WriterHandler rather than passing the full Connection.

Lmk what you think about this, we can chat about it in Discord also if something doesn't make sense!

from amqprs.

gftea avatar gftea commented on May 29, 2024

This is also unreliable since you do not control when the execution ended; the task might even be discarded before it was run.

In what cases you meant task will be discarded? Can you demonstrate such case in a test case?

let the ChannelDispatcher handle every frame.

If there are many channels, which dispatcher to handle connection class methods?

this means that whenever the last user-held Connection is dropped, everything will gracefully clean itself up:

AMQP requires before closing socket, there should be a handshake of Close/CloseOk, it requires gracefully shutdown AMQP connection before gracefully shutdown socket connection. I see we still a drop impl on connection to start handshake of Close/CloseOK, not only shutdown socket.

You do not need the DropGuard anymore, unless we want to make it safer for the user in case they forget to call .close() on the Channel, but that's up to you.

as explained above, we need to gracefully shutdown AMQP connection first, how can we avoid drop guard?

from amqprs.

McPatate avatar McPatate commented on May 29, 2024

In what cases you meant task will be discarded?

impl Drop for Connection {
    fn drop() {
        tokio::spawn(async move { /* whatever */ });
    }
}

fn main() {
    let conn = // Open connection;
}

When main() function exits, drop code is called but you cannot guarantee that the tokio runtime will be exited after the tokio::spawn was run.

If there are many channels, which dispatcher to handle connection class methods?

Good question, we could open a special private Channel for each connection that handles only the Connection related operations.

it requires gracefully shutdown AMQP connection before gracefully shutdown socket connection.

I think its fair to expect the user to close the connection with a call to .close() (like all the the other clients). Also, RabbitMQ knows the client disconnected since the TCP connection is closed, it's not as clean as sending manually a close but it works fine (you may lose messages in the case you auto-ack).
I think this is a fair trade-off, and it can be documented in the close method for instance, that if the method isn't called, you will potentially lose messages when you consume and do not ack messages.
Note that in this context, if your program exits unexpectedly, you may also lose messages:

Therefore, automatic message acknowledgement should be considered unsafe and not suitable for all workloads.
https://www.rabbitmq.com/confirms.html#acknowledgement-modes

from amqprs.

gftea avatar gftea commented on May 29, 2024

When main() function exits, drop code is called but you cannot guarantee that the tokio runtime will be exited after the tokio::spawn was run.

If tokio runtime exit, what is the point to do clean up? It seems to me in this case, it is user application issue.

Good question, we could open a special private Channel for each connection that handles only the Connection related operations.

Can you elobrate further? such as, how to sync between this private channel with last connection drop? what are the advantages over being hold in handler?

I think this is a fair trade-off, and it can be documented in the close method for instance, that if the method isn't called, you will potentially lose messages when you consume and do not ack messages.
Note that in this context, if your program exits unexpectedly, you may also lose messages:

Why we need the trade off while we already have a working solution to gracefully shutdown in both cases? I do not consider runtime exit is the case we need to properly handle.

I also see compliance of protocol is important for the library, e.g. two compliances

  • A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error
  • A peer that detects a socket closure without having received a Channel.Close-Ok handshake method SHOULD log the error.

from amqprs.

McPatate avatar McPatate commented on May 29, 2024

If tokio runtime exit, what is the point to do clean up? It seems to me in this case, it is user application issue.

I disagree, the library is responsible of cleaning up what it opened before the program exits.

In practice, you will usually create one amqp connection per rabbitmq server that will last the entirety of the program and the connection will be closed on shutdown. So you'll want resources to be cleaned up before exit.

Can you elobrate further? such as, how to sync between this private channel with last connection drop? what are the advantages over being hold in handler?

You can simply put the internal Channel as a field of the Connection and have an if in the dispatcher that checks if it is the id of the internal Channel to read the specific frames.
Note that the C# client has an internal channel, I suppose for this kind of purpose but I didn't check.

The issue with holding the Connection in the handlers is that you don't get drop behavior without re-implementing Drop for your type + having the need for a DropGuard.
Also, I don't think it's the library's responsibility to call close when the Connection is dropped, but the user's (it would be a nice to have, but it's not possible because of the async nature of the call).

I also see compliance of protocol is important for the library, e.g. two compliances

The behavior I'm describing is compliant with what you mention: I'm proposing to remove the current impl Drop entirely, so no more close call at drop() (there will be no close-ok expected, since no prior close will be sent to the server).

I understand that the current implementation works, but by updating to what I'm proposing, you get the following:

  • no need for tokio::sleep in tests
  • safe drop even without calling close (when consumers are not auto-ack)
    • actually you also have a safe drop in the current form, simply there is no guarantee that the tokio::spawn will be run when the program exits
  • less code

You can consider this as housekeeping, IMHO this would make the library a bit cleaner.

from amqprs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.