Skip to content

Is Cereal Thread Safe? #210

Closed
Closed
@ssbanerje

Description

@ssbanerje

I had a question about the thread safety of cereal. I have the following code, which uses cereal to serialize an int and a std::vector into a cereal::BinaryOutputArchive. It does 10000 of such conversions in parallel.

#include <iostream>
#include <cassert>
#include <vector>
#include <tuple>

#include <tbb/tbb.h>

#include <cereal/cereal.hpp>
#include <cereal/archives/binary.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/tuple.hpp>

std::string hexdump(const uint8_t* const begin, const size_t nbytes) {
  std::string str{""};
  const uint8_t* end = begin + nbytes;
  str.reserve((end - begin) * 2);
  const char *hex_chars = "0123456789ABCDEF";
  uint64_t ctr = 0;
  for (const uint8_t* it=begin; it < end; ++it) {
    uint8_t tmp = *it;
    ctr = (ctr + 1) % 16;
    char hex_string[4];
    hex_string[3] = '\0'; hex_string[2] = ' ';
    hex_string[1] = hex_chars[tmp % 16];
    tmp /= 16;
    hex_string[0] = hex_chars[tmp % 16];
    str += hex_string;
    if (ctr == 0) {
      str += "\n";
    }
  }
  return str;
}

class message {
 protected:
  char* buffer_;
  size_t buffer_length_;

 public:
  message() : buffer_(nullptr), buffer_length_(0) {}

  message(size_t length) {
    buffer_length_ = length;
    buffer_ = new char[length];
  }

  ~message() {
    if (buffer_) {
      delete [] buffer_;
      buffer_ = nullptr;
    }
  }

  inline char* buffer() const { return buffer_; }

  inline void set_buffer(char* buf, const int len) { buffer_ = buf; buffer_length_ = len; }

  inline unsigned buffer_length() const { return buffer_length_; }

  inline std::string dump_message() const {
    std::stringstream sstream;
    sstream << hexdump(reinterpret_cast<uint8_t*>(buffer_), buffer_length_);
    return sstream.str();
  }
};


class message_factory {
 public:
  template <typename... Args>
  static message create_message(Args&&... args) {
    std::stringstream stream;
    cereal::BinaryOutputArchive archive(stream);
    archive(std::forward<Args>(args)...)  ;
    auto buffer = stream.str().c_str();
    const size_t buffer_length = stream.str().size();
    message m(buffer_length);
    ::memcpy(m.buffer(), buffer, buffer_length*sizeof(char));
    return m;
  }

  template <typename... Args>
  inline static std::tuple<Args...> read_message_fields(message& mesg) {
    assert(mesg.buffer() != nullptr);
    std::stringstream stream(std::string(mesg.buffer(), mesg.buffer_length()));
    cereal::BinaryInputArchive archive(stream);
    std::tuple<Args...> data;
    archive(data);
    return data;
  }
};




int main() {
  std::vector<int> x{1,2,3,4,5,6,7,8,9,0};
  tbb::task_group g;
  for (int i = 0; i < 10000; i++) {
    g.run([&x] () {
      auto m = message_factory::create_message(1, x);
      auto v = message_factory::read_message_fields<int, std::vector<int> >(m);
      if (std::get<0>(v) != 1 || x != std::get<1>(v)) {
        std::cerr << m.dump_message() << std::endl;
      }
      assert(std::get<0>(v) == 1);
      assert(x == std::get<1>(v));
    });
  }
  g.wait();
  return EXIT_SUCCESS;
}

The output of this code is -

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0A 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
06 00 00 00 07 00 00 00 08 00 00 00 09 00 00 00
00 00 00 00
Assertion failed: (std::get<0>(v) == 1), function operator(), file .../test.cc, line 107.
[1]    26153 abort      ./TestApplication

I am guessing this is some sort of race condition as it does not always happen. Also, if I get rid of the threading code, this bug never shows up.

Any idea what is happening here? Is this a cereal bug?

I am using the latest version of clang on OS X

Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.4.0
Thread model: posix

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions