Bug 756795

Summary: Multiple clients sharing one session
Product: Red Hat Enterprise MRG Reporter: Petr Matousek <pematous>
Component: qpid-cppAssignee: Ken Giusti <kgiusti>
Status: NEW --- QA Contact: MRG Quality Engineering <mrgqe-bugs>
Severity: medium Docs Contact:
Priority: low    
Version: 2.0CC: jross, kgiusti
Target Milestone: ---   
Target Release: ---   
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Attachments:
Description Flags
python test reproduce
none
cpp test reproducer (does the same as the python one)
none
broker log file (snip)
none
test_reproducer shared/independent session none

Description Petr Matousek 2011-11-24 15:40:26 UTC
Description of problem:

When multiple clients sharing one session the behaviour of clients is different than with clients on independent sessions.

I have a simple script with one sender sending to a queue "q" asynchronously and one consumer consuming from another queue "q2" in one loop, the sender and consumer are sharing one session. The sender capacity can be adjusted by first cmdline argument. The consumer is active only if second cmdline argument was given. 

If the consumer is not active, the sender uses defined capacity for sending messages (messages are acked by broker after the defined capacity was reached).
But if the consumer is active (note: consuming messages on different queue than used by sender), the sender is also affected by the receiver.fetch() method call and all his unsettled messages are acknowledged. So the sender will never use his capacity in this case. 

The receiver.fetch() method is causing the broker to send a Session.completed command each time. It can be seen in attached the broker log file.
If the both test cases are executed against a broker with trace logging turned on, the test with both sender and receiver will get a session.completed after each fetch().  If running the test without the receiver, the broker sends the session.completed after the first 5 messages are sent.

I am not sure if the behaviour is wrong, but the behaviour is different when clients sharing one session than with clients on independent sessions.
If it is intended and the behaviour is expected, it shall definitely be documented so users are aware of it.

Feel free to change this bug to documentation bug.

This issue is also valid on latest 0.12 packages

Version-Release number of selected component (if applicable):
python-qpid-0.10-1.el5
python-qpid-qmf-0.10-10.el5
qpid-cpp-client-0.10-9.el5
qpid-cpp-client-devel-0.10-9.el5
qpid-cpp-client-devel-docs-0.10-9.el5
qpid-cpp-client-ssl-0.10-9.el5
qpid-cpp-server-0.10-9.el5
qpid-cpp-server-cluster-0.10-9.el5
qpid-cpp-server-devel-0.10-9.el5
qpid-cpp-server-ssl-0.10-9.el5
qpid-cpp-server-store-0.10-9.el5
qpid-cpp-server-xml-0.10-9.el5
qpid-java-client-0.10-9.el5
qpid-java-common-0.10-9.el5
qpid-java-example-0.10-9.el5
qpid-qmf-0.10-10.el5
qpid-qmf-devel-0.10-10.el5
qpid-tools-0.10-6.el5

How reproducible:
100%

Steps to Reproduce:
1. broker running
2. run the test reproducer with just one cmdline arg and check for unsettled messages
3. run the test reproducer with both cmdline args and check for unsettled messages
  
Actual results:
Different behaviour of clients sharing one session than clients on independent sessions

Expected results:
Different behaviour of clients sharing one session than clients on independent sessions or documentation update

Additional info:

Example test reproducer output:

# qpid_reboot; ./session.py 5
Stopping Qpid AMQP daemon:                                 [  OK  ]
Starting Qpid AMQP daemon:                                 [  OK  ]
sender capacity: 5
sending: Message 1
unsettled sender messages count: 1
sending: Message 2
unsettled sender messages count: 2
sending: Message 3
unsettled sender messages count: 3
sending: Message 4
unsettled sender messages count: 4
sending: Message 5
unsettled sender messages count: 5
sending: Message 6
unsettled sender messages count: 1
sending: Message 7
unsettled sender messages count: 2
sending: Message 8
unsettled sender messages count: 3
sending: Message 9
unsettled sender messages count: 4
sending: Message 10
unsettled sender messages count: 5

# qpid_reboot; ./session.py 5 1
Stopping Qpid AMQP daemon:                                 [  OK  ]
Starting Qpid AMQP daemon:                                 [  OK  ]
sender capacity: 5
sending: Message 1
unsettled sender messages count: 1
sending: Message 2
unsettled sender messages count: 1
sending: Message 3
unsettled sender messages count: 1
sending: Message 4
unsettled sender messages count: 1
sending: Message 5
unsettled sender messages count: 1
sending: Message 6
unsettled sender messages count: 1
sending: Message 7
unsettled sender messages count: 1
sending: Message 8
unsettled sender messages count: 1
sending: Message 9
unsettled sender messages count: 1
sending: Message 10
unsettled sender messages count: 1

Comment 1 Petr Matousek 2011-11-24 15:42:56 UTC
Created attachment 535958 [details]
python test reproduce

Comment 2 Petr Matousek 2011-11-24 15:44:57 UTC
Created attachment 535962 [details]
cpp test reproducer (does the same as the python one)

Comment 3 Petr Matousek 2011-11-24 15:47:51 UTC
Created attachment 535965 [details]
broker log file (snip)

Comment 4 Petr Matousek 2011-11-24 15:52:36 UTC
I'm a bit confused also with another test case:

If also third cmdline argument is given to the test reproducer, a new sender on another session is used to fill the "q2" with messages and timeout of receiver fetch() method is set to non-zero value.

In my opinion it shall be the same case as without this new sender, sender on one session shall not affect the unsettled messages on another session. So I would expected the number of unsettled messages to be 1 in each loop, because the receiver fetch() method was called. 

If session.acknowledge is used after the receiver fetch() method or fetch method uses timeout of 0, the result is as I would expect. (probably I do not correctly understand to the difference between session.acknowledge and session.completed methods)

moreover C++ client is giving different result than python client (I assume because it's faster? the output is always the same in my configuration).

Python:

# qpid_reboot; ./session.py 5 1 1
Stopping Qpid AMQP daemon:                                 [  OK  ]
Starting Qpid AMQP daemon:                                 [  OK  ]
sender capacity: 5
sending: Message 1
unsettled sender messages count: 1
sending: Message 2
unsettled sender messages count: 2
sending: Message 3
unsettled sender messages count: 3
sending: Message 4
unsettled sender messages count: 1
sending: Message 5
unsettled sender messages count: 2
sending: Message 6
unsettled sender messages count: 3
sending: Message 7
unsettled sender messages count: 1
sending: Message 8
unsettled sender messages count: 2
sending: Message 9
unsettled sender messages count: 3
sending: Message 10
unsettled sender messages count: 1

C++:

# qpid_reboot; ./session.out 5 1 1
Stopping Qpid AMQP daemon:                                 [  OK  ]
Starting Qpid AMQP daemon:                                 [  OK  ]
sender capacity: 5
sending: Message 1
unsettled sender messages count: 1
sending: Message 2
unsettled sender messages count: 1
sending: Message 3
unsettled sender messages count: 2
sending: Message 4
unsettled sender messages count: 3
sending: Message 5
unsettled sender messages count: 2
sending: Message 6
unsettled sender messages count: 3
sending: Message 7
unsettled sender messages count: 2
sending: Message 8
unsettled sender messages count: 3
sending: Message 9
unsettled sender messages count: 2
sending: Message 10
unsettled sender messages count: 3

Comment 5 Justin Ross 2011-11-28 14:59:05 UTC
Ken, is there any unexpected behavior here?

Comment 6 Ken Giusti 2011-11-28 17:05:12 UTC
(In reply to comment #4)
> I'm a bit confused also with another test case:
> 
> If also third cmdline argument is given to the test reproducer, a new sender on
> another session is used to fill the "q2" with messages and timeout of receiver
> fetch() method is set to non-zero value.
> 
> In my opinion it shall be the same case as without this new sender, sender on
> one session shall not affect the unsettled messages on another session. So I
> would expected the number of unsettled messages to be 1 in each loop, because
> the receiver fetch() method was called. 
> 

The difference you are seeing is due to the fetch actually returning a message in this case.  This causes the broker to perform an additional message.transfer command, before replenishing the session's credit.  So, depending on the timing of the calls, you're more likely to see the credit value change in this case.

This seems well within my understanding of the design, since credit is being replenished correctly, just at a different pace.

> If session.acknowledge is used after the receiver fetch() method or fetch
> method uses timeout of 0, the result is as I would expect. (probably I do not
> correctly understand to the difference between session.acknowledge and
> session.completed methods)
> 
> moreover C++ client is giving different result than python client (I assume
> because it's faster? the output is always the same in my configuration).
> 
> Python:
> 
> # qpid_reboot; ./session.py 5 1 1
> Stopping Qpid AMQP daemon:                                 [  OK  ]
> Starting Qpid AMQP daemon:                                 [  OK  ]
> sender capacity: 5
> sending: Message 1
> unsettled sender messages count: 1
> sending: Message 2
> unsettled sender messages count: 2
> sending: Message 3
> unsettled sender messages count: 3
> sending: Message 4
> unsettled sender messages count: 1
> sending: Message 5
> unsettled sender messages count: 2
> sending: Message 6
> unsettled sender messages count: 3
> sending: Message 7
> unsettled sender messages count: 1
> sending: Message 8
> unsettled sender messages count: 2
> sending: Message 9
> unsettled sender messages count: 3
> sending: Message 10
> unsettled sender messages count: 1
> 
> C++:
> 
> # qpid_reboot; ./session.out 5 1 1
> Stopping Qpid AMQP daemon:                                 [  OK  ]
> Starting Qpid AMQP daemon:                                 [  OK  ]
> sender capacity: 5
> sending: Message 1
> unsettled sender messages count: 1
> sending: Message 2
> unsettled sender messages count: 1
> sending: Message 3
> unsettled sender messages count: 2
> sending: Message 4
> unsettled sender messages count: 3
> sending: Message 5
> unsettled sender messages count: 2
> sending: Message 6
> unsettled sender messages count: 3
> sending: Message 7
> unsettled sender messages count: 2
> sending: Message 8
> unsettled sender messages count: 3
> sending: Message 9
> unsettled sender messages count: 2
> sending: Message 10
> unsettled sender messages count: 3

Comment 7 Petr Matousek 2011-12-01 13:34:14 UTC
First of all, I precede that I'm not saying that the behaviour is wrong, I just don't understand it well and I am trying to catch the point. 

In my opinion this issue is not affected by the session's credit, the log shows sufficient credit in each loop, I have also tried to set the limit on the sender and the consumer to some big value (say 1000) and the results are not affected by this. The numbers in the output of the test reproducer are not related to credit but to messages not acked by the broker (the usage of sender capacity - unsettled messages).

In my opinion it is also not related to the message.transfer command because if you put the sender and the consumer on different sessions same behaviour as reported in Comment 4 can be seen regardless if the message.transfer command was issued or not (regardless the queue from which the consumer is consuming messages is empty or not).  This is similar to the  original issue reported - different behaviour of client sharing one session vs. behaviour of clients on different session. When consuming from empty queue using one session for both sender and receiver the sent messages are acked by the broker in each loop, when the clients are on different sessions the sent messages are not acked by the broker in each loop. So the session.completed command is not invoked after every fetch() method, as you've mentioned in our previous conversation. 

The question here is how the acks shall be sent by the broker (when the session.complete command shall be triggered)? Can they arrive at any time? or they shall be acked after the full sender capacity was reached?

What I see here, the behaviour on different sessions is that the sent messages are acked by the broker at next message after the border of 50% capacity was reached. It can be seen by changing the value of first argument of the test reproducer.

In any case, the behaviour is that all the messages are correctly delivered, the sent messages are acked in the range of sender capacity which is correct as well, so maybe only the difference in acking between clients sharing one session and clients on different session shall be pointed out in our programming guide so users are aware of it. The behaviour is independent on the sender/consumer address. (acts the same  regardless the clients are using the same address or not).

I am also wondering about the session acknowledge() behaviour, if performed after the send method call in loop, the messages are not acked in acknowlegde method (with session sync() they naturally do). The messages are always acked during send method call after the capacity is reached. It can be demonstrated using the following code:

  for i in range(msg_cnt):
    sender.send(Message("Message %s" % (i+1)), sync=False)
    print "unsettled sender messages count: %s" % (sender.unsettled())
    session.acknowledge()
    print "unsettled sender messages count: %s" % (sender.unsettled())


Question summary:
1] When it is expected broker should send back the message acks? (assuming the sender is using asynchronous send operations and broker just confirms by ack that received from client the message)
2] Does the receiver.fetch() always trigger the session_complete() which triggers delivery of all pending send operation acks?
3] What happens when a client uses sender.send() interleaved with session.acknowledge() ?

Thanks in advance for clarification.

Comment 8 Petr Matousek 2011-12-01 13:36:42 UTC
Created attachment 539206 [details]
test_reproducer shared/independent session

I am attaching a new test reproducer that shows the different behaviour on the shared/independent session more clearly.
example use: 
clients on shared session, sender capacity=5:
# ./session.py 5 1
clients on independent session, sender capacity=5:
# ./session.py 5 0