1

I am using boost's message queue to write a basic class with just two char arrays, but the data is not being received in the second process is empty, even though get_num_msg() returns 1 before the read and returns 0 after reading. For debugging purposes I also tried writing and reading from the same process, and that worked fine. I am using the shared pointer because earlier while just reading and writing integers, it would not read the integer at the receiver unless it was declared as shared ptr.

AccessQueue

class AccessQueue {
    public:
    char name[64];
    char action[64];
    AccessQueue(char name[64], char action[64]) {
        strcpy(this->name, name);
        strcpy(this->action, action);
    }
    AccessQueue() {}
};

sending function

// std::shared_ptr<AccessQueue> p1;
this->p1.reset(new AccessQueue("asd", "vsq"));
try {
    this->mq->send(&p1, sizeof(p1), 0);
} catch(boost::interprocess::interprocess_exception & ex) {
    std::cout << ex.what() << std::endl;
}

receiving function

std::cout << this->mq->get_num_msg() << "\t" << this->mq->get_max_msg_size() << "\t" << this->mq->get_max_msg() << std::endl;
AccessQueue * a;
unsigned int priority;
boost::interprocess::message_queue::size_type recvd_size;
try {
    this->mq->try_receive(&a, sizeof(AccessQueue), recvd_size, priority);
} catch(boost::interprocess::interprocess_exception & ex) {
    std::cout << ex.what() << std::endl;
}
std::cout << this->mq->get_num_msg() << "\t" << this->mq->get_max_msg_size() << "\t" << this->mq->get_max_msg() << std::endl;
std::cout << "It clearly maybe works " << a->action << "\t" << a->name << std::endl;

output at receiver's end:

1       128     20
0       128     20
t348575
  • 356
  • 3
  • 12

1 Answers1

1

Looks like p1 (in the sending function) is a smart pointer (like std::unique_ptr or std::shared_ptr). In that case

this->mq->send(&p1, sizeof(p1), 0);

is obviously wrong, because it puts the pointer object on the queue, instead of the data structure. Use

this->mq->send(*p1, sizeof(*p1), 0);

Or, indeed, don't use dynamic allocation in the first place:

AccessQueue packet("asd", "vsq");
mq.send(&packet, sizeof(packet), 0);

Uhoh there's more

On the receiving side, there's a similar problem:

    AccessQueue * a;
    // ..
    mq.try_receive(&a, sizeof(AccessQueue), ...);

That receives INTO the pointer, not the object. You don't even have an object, because a (the pointer) is never initialized. Here the fix is syntactically simple:

    AccessQueue a;

No more pointers. Now, a is an object and &a is the address of that object.

Note how the original was UB because you read sizeof(AccessQueue) bytes into a pointer. However the pointer is only 8 bytes and the struct is 128 bytes. Ooops!

Simplified Working Demo

This works:

Live On Wandbox¹

#include <boost/interprocess/ipc/message_queue.hpp>
#include <iostream>
#include <iomanip>

namespace bip = boost::interprocess;
using MQ = bip::message_queue;

template<size_t N>
static inline void safe_copy(std::array<char, N>& dst, std::string_view src) {
    std::copy_n(src.data(), std::min(src.size(), N), dst.data());
    dst.back() = 0; // make sure of NUL termination
}

struct AccessQueue {
    std::array<char, 64> name{0};
    std::array<char, 64> action{0};

    AccessQueue(std::string_view n = "", std::string_view a = "") {
        safe_copy(name, n);
        safe_copy(action, a);
    }
};

static_assert(std::is_standard_layout_v<AccessQueue>);

struct X {
    void send() {
        AccessQueue packet("asd", "vsq");
        try {
            mq.send(&packet, sizeof(packet), 0);
        } catch(std::exception const & ex) {
            std::cout << ex.what() << std::endl;
        }
    }

    AccessQueue receive() {
        AccessQueue retval;
        
        report();
        try {
            unsigned int priority;
            MQ::size_type recvd_size;
            mq.try_receive(&retval, sizeof(AccessQueue), recvd_size, priority);
        } catch(std::exception const & ex) {
            std::cout << ex.what() << std::endl;
        }
        report();
        return retval;
    }

    void report() {
        std::cout << mq.get_num_msg() << "\t" << mq.get_max_msg_size() << "\t" << mq.get_max_msg() << std::endl;
    }

    MQ mq { bip::open_or_create, "somequeue", 10, sizeof(AccessQueue) };
};

int main() {
    X tryit;
    tryit.send();
    auto const& [name, action] = tryit.receive();

    std::cout << std::quoted(name.data()) << " " << std::quoted(action.data()) << std::endl;
}

Prints

1       128     10
0       128     10
"asd" "vsq"

Note

¹ shared emmory is prohibited on Wandbox :(

sehe
  • 328,274
  • 43
  • 416
  • 565
  • Added a reviewd working demo – sehe Dec 10 '20 at 23:58
  • Here's a demo of it in separate processes in case it helps: https://imgur.com/R5fwDhh (slightly [modified `main`](https://wandbox.org/permlink/1giDsH026B5YFrYX) to accept commandline argument) – sehe Dec 11 '20 at 00:10
  • thank you. Are boost tcp sockets better than (higher throughput) message queue if I were only doing IPC within the system? (I am trying to create a caching solution to be used with nodejs as an addon) – t348575 Dec 11 '20 at 05:24
  • I'd say IPC on the same machine I'd say shared memory is likely the fastest (synchronization like between threads in the same process: even lockfree options exist). Message-Queueing has strictly more features than sockets (the transactionality of delivery). So it makes sense that it is more costly, but you can't compare these. – sehe Dec 11 '20 at 20:08