Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One NodeShared per process #152

Merged
merged 16 commits into from
Sep 4, 2020
4 changes: 4 additions & 0 deletions include/ignition/transport/Helpers.hh
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ namespace ignition
const std::string &_orig,
char _delim);

/// \brief Portable function to get the id of the current process.
/// \returns id of current process
unsigned int IGNITION_TRANSPORT_VISIBLE getProcessId();

// Use safer functions on Windows
#ifdef _MSC_VER
#define ign_strcat strcat_s
Expand Down
16 changes: 16 additions & 0 deletions src/Helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
#include <cstdlib>
#include <string>

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

#include "ignition/transport/Helpers.hh"

namespace ignition
Expand Down Expand Up @@ -59,6 +65,16 @@ namespace ignition
pieces.push_back(_orig.substr(pos1, _orig.size()-pos1));
return pieces;
}

//////////////////////////////////////////////////
unsigned int getProcessId()
{
#ifdef _WIN32
return ::GetCurrentProcessId();
#else
return ::getpid();
#endif
}
}
}
}
62 changes: 41 additions & 21 deletions src/NodeShared.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
#include <iostream>
#include <map>
#include <mutex>
#include <shared_mutex> //NOLINT
#include <string>
#include <thread>
#include <vector>
#include <unordered_map>

// TODO(anyone): Remove after fixing the warnings.
#ifdef _MSC_VER
Expand Down Expand Up @@ -168,27 +170,45 @@ void sendAuthErrorHelper(zmq::socket_t &_socket, const std::string &_err)
//////////////////////////////////////////////////
NodeShared *NodeShared::Instance()
{
#ifdef _MSC_VER
// If we compile ign-transport as a shared library on Windows, we should
// never destruct NodeShared, unfortunately. It seems that WinSock does
// not behave well during the DLL teardown phase as a program exits, and
// this will confuse the ZeroMQ library into thinking that WinSock
// misbehaved, causing an assertion in ZeroMQ to fail and throw an exception
// while the program exits. This is a known issue:
//
// https://github.com/zeromq/libzmq/issues/1144
//
// An easy way of dodging this issue is to never destruct NodeShared. The
// Operating System will take care of cleaning up its resources when the
// application exits. We may want to consider a more elegant solution in
// the future. The zsys_shutdown() function in the czmq library may be able
// to provide some inspiration for solving this more cleanly.
static NodeShared *instance = new NodeShared();
return instance;
#else
static NodeShared instance;
return &instance;
#endif
// Create an instance of NodeShared per process so the ZMQ context
// is not shared between different processes.

static std::shared_mutex mutex;
static std::unordered_map<unsigned int, NodeShared*> nodeSharedMap;

// Get current process ID.
auto pid = getProcessId();

// Check if there's already a NodeShared instance for this process.
// Use a shared_lock so multiple threads can read simultaneously.
// This will only block if there's another thread locking exclusively
// for writing. Since most of the time threads will be reading,
// we make the read operation faster at the expense of making the write
// operation slower. Use exceptions for their zero-cost when successful.
try
{
std::shared_lock readLock(mutex);
return nodeSharedMap.at(pid);
}
catch (...)
{
// Multiple threads from the same process could have arrived here
// simultaneously, so after locking, we need to make sure that there's
// not an already constructed NodeShared instance for this process.
std::lock_guard writeLock(mutex);

auto iter = nodeSharedMap.find(pid);
if (iter != nodeSharedMap.end())
{
// There's already an instance for this process, return it.
return iter->second;
}

// No instance, construct a new one.
auto ret = nodeSharedMap.insert({pid, new NodeShared});
assert(ret.second); // Insert operation should be successful.
return ret.first->second;
}
}

//////////////////////////////////////////////////
Expand Down