Bug 1316507
| Summary: | [C++ client] Message::setContent("") does not work | ||
|---|---|---|---|
| Product: | Red Hat Enterprise MRG | Reporter: | Pavel Moravec <pmoravec> |
| Component: | qpid-cpp | Assignee: | messaging-bugs <messaging-bugs> |
| Status: | CLOSED ERRATA | QA Contact: | Messaging QE <messaging-qe-bugs> |
| Severity: | high | Docs Contact: | |
| Priority: | high | ||
| Version: | 3.2 | CC: | gsim, jross, mcressma, tkratky, zkraus |
| Target Milestone: | 3.2.2 | ||
| Target Release: | --- | ||
| Hardware: | All | ||
| OS: | Linux | ||
| Whiteboard: | |||
| Fixed In Version: | qpid-cpp-0.34-10 | Doc Type: | Bug Fix |
| Doc Text: |
Cause: The message implementation can hold two views of message content: the raw bytes or a decode object form. These operate independently, i.e. changes to the raw bytes do not alter the object content.
Consequence: When a message is automatically decoded into object form on receipt, then updated by setting the raw bytes in order to resend, the resent message used the original object content rather than the bytes.
Fix: On setting the raw bytes, any previously held object content view is reset (i.e. invalidated). (Additionally, a setting was added to allow the automatic decoding of messages into object form to be disabled by environment variable or config file).
Result: The message content reflects the applications actions.
|
Story Points: | --- |
| Clone Of: | Environment: | ||
| Last Closed: | 2016-10-11 07:36:14 UTC | Type: | Bug |
| Regression: | --- | Mount Type: | --- |
| Documentation: | --- | CRM: | |
| Verified Versions: | Category: | --- | |
| oVirt Team: | --- | RHEL 7.3 requirements from Atomic Host: | |
| Cloudforms Team: | --- | Target Upstream Version: | |
| Embargoed: | |||
A workaround is to setContentObject(std::string()), or getContentObject().reset(). Fixed upstream: https://svn.apache.org/r1734476 This bug was accidentally moved from POST to MODIFIED via an error in automation, please see mmccune with any questions The fix does not work and some more generic problem identified (let split to 2nd bug it if you feel a separate BZ needed for it).
Reproducer: use amqp/map content type, copy such message, update content (to "" or to some non-empty string, shorter than original one), send to qpidd, receive - the received message will have the _original_ content, still
Reproducer source code:
#include <iostream>
#include <string>
#include <unistd.h>
#include <qpid/messaging/Address.h>
#include <qpid/messaging/Connection.h>
#include <qpid/messaging/Session.h>
#include <qpid/messaging/Message.h>
#include <qpid/messaging/Sender.h>
#include <qpid/messaging/Receiver.h>
#include <qpid/types/Variant.h>
using namespace std;
using namespace qpid::messaging;
using namespace qpid::types;
const int fragSize = 102;
const int msgSize = 107;
int
main( int argc, char *argv[])
{
//enable it to test the content type amqp/map
#if 1
Message message;
qpid::types::Variant::Map params;
params["payload"] = std::string(msgSize, 'a');
encode( params, message);
message.setCorrelationId("123");
cout << "message content: " << message.getContent() << endl;
#endif
//enable it to test the content type empty
#if 0
qpid::types::Variant::Map params;
std::string content;
for (int i = 0; i < msgSize; i++)
content+="A";
Message message(content);
cout << "message content: " << message.getContent() << endl;
#endif
Connection connection = Connection("localhost:5672");
connection.open();
Session session = connection.createSession();
Sender sender = session.createSender("amq.direct");
message.setSubject("IN");
// message.setContentType("amqp/map"); // already set
cout << "message sent:\n size : " << message.getContentSize() << " type : " << message.getContentType() << " cID : " << message.getCorrelationId() << std::endl;
sender.send(message);
Message c_message;
Connection c_connection("localhost:5672");
c_connection.open();
Session c_session = c_connection.createSession();
Receiver receiver = c_session.createReceiver("testQueueIn");
receiver.fetch(c_message, Duration::FOREVER);
cout << "message received:\n size : " << c_message.getContentSize() << " type : " << c_message.getContentType() << " cID: " << c_message.getCorrelationId() << std::endl;
c_connection.close();
Message fragTemplate(c_message);
for( int sequenceNumber = 1; (sequenceNumber-1) * fragSize <= (signed int)c_message.getContentSize() ; sequenceNumber++)
{
Message fragment(fragTemplate);
fragment.setContent(c_message.getContent().substr((sequenceNumber-1) * fragSize, fragSize));
if( sequenceNumber == 1 )
fragment.setCorrelationId( "1");
else
fragment.setCorrelationId ("2");
fragment.setSubject("OUT");
// fragment.setContentType("amqp/map"); // already set
cout << "fragment sent:\n size: " << fragment.getContentSize() << " type: " << fragment.getContentType() << " cID : " << fragment.getCorrelationId() << endl;
cout << "fragment content: " << fragment.getContent() << endl;
sender.send(fragment);
session.sync();
}
for( int i = 0; i < 2 ; ++i) {
Message r_message;
Connection r_connection("localhost:5672");
r_connection.open();
Session r_session = r_connection.createSession();
Receiver r_receiver = r_session.createReceiver("testQueueOut");
r_receiver.fetch(r_message, Duration::IMMEDIATE);
r_session.acknowledge(true);
cout << "message received:\n size : " << r_message.getContentSize() << " type : " << r_message.getContentType() << " cID : " << r_message.getCorrelationId() << std::endl;
cout << "message content: " << r_message.getContent() << endl;
r_connection.close();
}
return 0;
}
Setup to reproduce it:
qpid-config add queue testQueueIn
qpid-config add queue testQueueOut
qpid-config bind amq.direct testQueueIn IN
qpid-config bind amq.direct testQueueOut OUT
Running the reproducer on 0.34-8:
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
message sent:
size : 126 type : amqp/map cID : 123
message received:
size : 126 type : amqp/map cID: 123
fragment sent:
size: 102 type: amqp/map cID : 1
fragment content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
fragment sent:
size: 24 type: amqp/map cID : 2
fragment content: aaaaaaaaaaaaaaaaaaaaaaaa
message received:
size : 126 type : amqp/map cID : 1
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
message received:
size : 126 type : amqp/map cID : 2
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
See the "fragment content" is OK, but even sending it to the broker, the original content is used (run tcpdump to confirm).
Making the scenario worse:
having line 68:
fragment.setContent("");
the program coredumps:
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
message sent:
size : 126 type : amqp/map cID : 123
message received:
size : 126 type : amqp/map cID: 123
fragment sent:
size: 0 type: amqp/map cID : 1
fragment content:
fragment sent:
size: 0 type: amqp/map cID : 2
fragment content:
terminate called after throwing an instance of 'qpid::messaging::EncodingException'
what(): illegal-argument: Not enough data for field table. (/builddir/build/BUILD/qpid-cpp-0.34/src/qpid/framing/FieldTable.cpp:295)
Aborted (core dumped)
FYI the reproducer works fine in upstream, so a difference between upstream (r1737000) and qpid-cpp 0.34-8 causes the problems. I see the same behaviour upstream as well as on 0.34-mrg, namely that the bytes actually sent are the original full map, not the fragments as would be expected. This is because the map is decoded in the receiver (in order for the behaviour between 0-10 and 1.0 to be uniform). There is a workaround, which is to set a connection option on c_connection and r_connection of "disable_auto_decode" to true. The fix is to remove the condition from the original fix, i.e. reset the content-object whenever the bytes are changed. However with that fix in place, if the auto-decode is not disabled then the fetch will fail as the message is considered malformed (it has a content-type that indicates it is a map, but the content itself is not a valid map). The fetch throws an exception, which if caught will prevent the core dump. So to summarise, there was a new behaviour introduced that will auto-decode maps/lists for AMQP 0-10. This can be disabled via a connection option. If disabled, it works around the bug exposed by the latest reproducer. Even if the bug is fixed, the new auto-decoding behaviour would cause problems if messages are ever sent with content that does not match the content type. One possibility would be to add a way to alter the default value for disable_auto_decode, so that could be set in an env var or conf file and would apply to all connections without modifying any code. Fixed[1] the bug that prevents updated bytes resetting the content-object (this is really an extension to the first fix made for this issue to cover all cases). Also added the ability to set the disable-auto-decode option via an environment variable or client config file[2]. [1] https://svn.apache.org/r1737093 [2] https://svn.apache.org/r1737094 Since the problem described in this bug report should be resolved in a recent advisory, it has been closed with a resolution of ERRATA. For information on the advisory, and where to find the updated files, follow the link below. If the solution does not work for you, open a new bug report. https://rhn.redhat.com/errata/RHBA-2016-2049.html |
Description of problem: Message::setContent internally updates just "bytes" property of the Message object but not the "content" object. That brings problems when trying to reset content to empty one - original content is still stored in "content" object property and e.g. an attempt to send this "empty" message sends the message with original content. Version-Release number of selected component (if applicable): qpid-cpp 0.34-5 How reproducible: 100% Steps to Reproduce: 1. Have trivial program: #include <iostream> #include <qpid/messaging/Message.h> using namespace std; using namespace qpid::messaging; int main(int argc, char* argv[]) { qpid::types::Variant content("some content"); Message m1(content); cout << "Message 1: initial content set to \"" << m1.getContent() << "\", contentSize = " << m1.getContentSize() << endl; m1.setContent("message 1"); cout << "Message 1: after content set to \"" << m1.getContent() << "\", contentSize = " << m1.getContentSize() << endl; m1.setContent(std::string()); cout << "Message 1: after content set to an empty string, the content is still \"" << m1.getContent() << "\" and contentSize = " << m1.getContentSize() << endl; return 0; } 2. (optionally, get the message from the broker and send it back to ensure proper behaviour; in this case test both AMQP 0-10 (should be broken) and AMQP 1.0 (should work even now). 3. run the program Actual results: Message 1: initial content set to "some content", contentSize = 12 Message 1: after content set to "message 1", contentSize = 9 Message 1: after content set to an empty string, the content is still "some content" and contentSize = 12 Expected results: Message 1: initial content set to "some content", contentSize = 12 Message 1: after content set to "message 1", contentSize = 9 Message 1: after content set to an empty string, the content is still "" and contentSize = 0 Additional info: See "some content" of size 12 returned after an attempt to empty the message content. See it is the original content before first setContent called.