Comments (8)
summary to close this ticket
- I would prefer to keep the feature that try best effort to close connection and channel at drop, so keeping drop guard design
- the runtime exit case is not considered a use case needed to be handled in the library
from amqprs.
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.
@McPatate you can try v0.9.1, which includes #31
from amqprs.
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 upReaderHandler
ChannelManager
will be dropped, dropping everydispatcher_tx
stopping everydispatcher.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.
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 theChannel
, 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.
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.
When
main()
function exits, drop code is called but you cannot guarantee that the tokio runtime will be exited after thetokio::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 theConnection
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.
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
- actually you also have a safe drop in the current form, simply there is no guarantee that the
- less code
You can consider this as housekeeping, IMHO this would make the library a bit cleaner.
from amqprs.
Related Issues (20)
- Error running the publisher subsriber example. HOT 4
- Consumer immediately gets closed HOT 2
- BasicProperties: expose delivery mode (persistent vs. transient) as a boolean or enum HOT 3
- "Alternative default: queue declaration arguments HOT 1
- BasicPublishArguments: deprecate the immediate field HOT 2
- BasicConsumeArguments: rename no_ack since it is confusing HOT 3
- Address (most) clippy warnings HOT 3
- ConsumerMessage.delivery and .basic_properties should not be options HOT 2
- ExchangeDeclareArguments: exchange type is inherently an enum HOT 3
- FieldTable needs a convenience method for instantiation from a map HOT 4
- Best practices for opening channel, and prevent it from being dropped. HOT 4
- FieldTable does not accept integer type HOT 3
- Implementing Prefetch for a given Queue using AsyncConsumer HOT 2
- Feature request: Support for external authentication mechanism HOT 3
- ChannelCallback::cancel() not called when server delete queue HOT 3
- Support `OpenConnectionArguments::from_url`? HOT 2
- Parsing Headers into Strings?
- Parsing Headers into Strings? HOT 3
- "channel concurrency" inconsistency between documentation & implementation HOT 1
- [feature request] Missing heartbeat should trigger an I/O failure HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from amqprs.