Files
allwpilib/ntcore/src/main/native/cpp/CallbackManager.h
Peter Johnson f84018af5f Move entirety of llvm namespace to wpi namespace.
During shared library loading, a different libLLVM can be pulled in, causing
llvm symbols from dependent libraries to resolve to that library instead of
this one. This has been seen in the wild with the Mesa OpenGL implementation
in JavaFX applications (see wpilibsuite/shuffleboard#361).

This is clearly a very breaking change. For some level of backwards
compatibility, a namespace alias from llvm to wpi is performed in the "llvm"
headers.  Unfortunately, forward declarations of llvm classes will still break,
but compilers seem to generate clear error messages in those cases
("namespace alias 'llvm' not allowed here, assuming 'wpi'").

This change also moves all the wpiutil headers to a single "wpi" subdirectory
from the previously split "llvm", "support", "tcpsockets", and "udpsockets".
Shim headers will be added for backwards compatibility in a later commit.
2018-04-30 10:22:54 -07:00

341 lines
11 KiB
C++

/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2017-2018. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#ifndef NTCORE_CALLBACKMANAGER_H_
#define NTCORE_CALLBACKMANAGER_H_
#include <atomic>
#include <climits>
#include <functional>
#include <memory>
#include <queue>
#include <utility>
#include <vector>
#include <wpi/SafeThread.h>
#include <wpi/UidVector.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/raw_ostream.h>
namespace nt {
namespace impl {
template <typename Callback>
class ListenerData {
public:
ListenerData() = default;
explicit ListenerData(Callback callback_) : callback(callback_) {}
explicit ListenerData(unsigned int poller_uid_) : poller_uid(poller_uid_) {}
explicit operator bool() const { return callback || poller_uid != UINT_MAX; }
Callback callback;
unsigned int poller_uid = UINT_MAX;
};
// CRTP callback manager thread
// @tparam Derived derived class
// @tparam NotifierData data buffered for each callback
// @tparam ListenerData data stored for each listener
// Derived must define the following functions:
// bool Matches(const ListenerData& listener, const NotifierData& data);
// void SetListener(NotifierData* data, unsigned int listener_uid);
// void DoCallback(Callback callback, const NotifierData& data);
template <typename Derived, typename TUserInfo,
typename TListenerData =
ListenerData<std::function<void(const TUserInfo& info)>>,
typename TNotifierData = TUserInfo>
class CallbackThread : public wpi::SafeThread {
public:
typedef TUserInfo UserInfo;
typedef TNotifierData NotifierData;
typedef TListenerData ListenerData;
~CallbackThread() {
// Wake up any blocked pollers
for (size_t i = 0; i < m_pollers.size(); ++i) {
if (auto poller = m_pollers[i]) poller->Terminate();
}
}
void Main() override;
wpi::UidVector<ListenerData, 64> m_listeners;
std::queue<std::pair<unsigned int, NotifierData>> m_queue;
wpi::condition_variable m_queue_empty;
struct Poller {
void Terminate() {
{
std::lock_guard<wpi::mutex> lock(poll_mutex);
terminating = true;
}
poll_cond.notify_all();
}
std::queue<NotifierData> poll_queue;
wpi::mutex poll_mutex;
wpi::condition_variable poll_cond;
bool terminating = false;
bool cancelling = false;
};
wpi::UidVector<std::shared_ptr<Poller>, 64> m_pollers;
// Must be called with m_mutex held
template <typename... Args>
void SendPoller(unsigned int poller_uid, Args&&... args) {
if (poller_uid > m_pollers.size()) return;
auto poller = m_pollers[poller_uid];
if (!poller) return;
{
std::lock_guard<wpi::mutex> lock(poller->poll_mutex);
poller->poll_queue.emplace(std::forward<Args>(args)...);
}
poller->poll_cond.notify_one();
}
};
template <typename Derived, typename TUserInfo, typename TListenerData,
typename TNotifierData>
void CallbackThread<Derived, TUserInfo, TListenerData, TNotifierData>::Main() {
std::unique_lock<wpi::mutex> lock(m_mutex);
while (m_active) {
while (m_queue.empty()) {
m_cond.wait(lock);
if (!m_active) return;
}
while (!m_queue.empty()) {
if (!m_active) return;
auto item = std::move(m_queue.front());
if (item.first != UINT_MAX) {
if (item.first < m_listeners.size()) {
auto& listener = m_listeners[item.first];
if (listener &&
static_cast<Derived*>(this)->Matches(listener, item.second)) {
static_cast<Derived*>(this)->SetListener(&item.second, item.first);
if (listener.callback) {
lock.unlock();
static_cast<Derived*>(this)->DoCallback(listener.callback,
item.second);
lock.lock();
} else if (listener.poller_uid != UINT_MAX) {
SendPoller(listener.poller_uid, std::move(item.second));
}
}
}
} else {
// Use index because iterator might get invalidated.
for (size_t i = 0; i < m_listeners.size(); ++i) {
auto& listener = m_listeners[i];
if (!listener) continue;
if (!static_cast<Derived*>(this)->Matches(listener, item.second))
continue;
static_cast<Derived*>(this)->SetListener(&item.second, i);
if (listener.callback) {
lock.unlock();
static_cast<Derived*>(this)->DoCallback(listener.callback,
item.second);
lock.lock();
} else if (listener.poller_uid != UINT_MAX) {
SendPoller(listener.poller_uid, item.second);
}
}
}
m_queue.pop();
}
m_queue_empty.notify_all();
}
}
} // namespace impl
// CRTP callback manager
// @tparam Derived derived class
// @tparam Thread custom thread (must be derived from impl::CallbackThread)
//
// Derived must define the following functions:
// void Start();
template <typename Derived, typename Thread>
class CallbackManager {
friend class RpcServerTest;
public:
void Stop() { m_owner.Stop(); }
void Remove(unsigned int listener_uid) {
auto thr = m_owner.GetThread();
if (!thr) return;
thr->m_listeners.erase(listener_uid);
}
unsigned int CreatePoller() {
static_cast<Derived*>(this)->Start();
auto thr = m_owner.GetThread();
return thr->m_pollers.emplace_back(
std::make_shared<typename Thread::Poller>());
}
void RemovePoller(unsigned int poller_uid) {
auto thr = m_owner.GetThread();
if (!thr) return;
// Remove any listeners that are associated with this poller
for (size_t i = 0; i < thr->m_listeners.size(); ++i) {
if (thr->m_listeners[i].poller_uid == poller_uid)
thr->m_listeners.erase(i);
}
// Wake up any blocked pollers
if (poller_uid >= thr->m_pollers.size()) return;
auto poller = thr->m_pollers[poller_uid];
if (!poller) return;
poller->Terminate();
return thr->m_pollers.erase(poller_uid);
}
bool WaitForQueue(double timeout) {
auto thr = m_owner.GetThread();
if (!thr) return true;
auto& lock = thr.GetLock();
#if defined(_MSC_VER) && _MSC_VER < 1900
auto timeout_time = std::chrono::steady_clock::now() +
std::chrono::duration<int64_t, std::nano>(
static_cast<int64_t>(timeout * 1e9));
#else
auto timeout_time = std::chrono::steady_clock::now() +
std::chrono::duration<double>(timeout);
#endif
while (!thr->m_queue.empty()) {
if (!thr->m_active) return true;
if (timeout == 0) return false;
if (timeout < 0) {
thr->m_queue_empty.wait(lock);
} else {
auto cond_timed_out = thr->m_queue_empty.wait_until(lock, timeout_time);
if (cond_timed_out == std::cv_status::timeout) return false;
}
}
return true;
}
std::vector<typename Thread::UserInfo> Poll(unsigned int poller_uid) {
bool timed_out = false;
return Poll(poller_uid, -1, &timed_out);
}
std::vector<typename Thread::UserInfo> Poll(unsigned int poller_uid,
double timeout, bool* timed_out) {
std::vector<typename Thread::UserInfo> infos;
std::shared_ptr<typename Thread::Poller> poller;
{
auto thr = m_owner.GetThread();
if (!thr) return infos;
if (poller_uid > thr->m_pollers.size()) return infos;
poller = thr->m_pollers[poller_uid];
if (!poller) return infos;
}
std::unique_lock<wpi::mutex> lock(poller->poll_mutex);
#if defined(_MSC_VER) && _MSC_VER < 1900
auto timeout_time = std::chrono::steady_clock::now() +
std::chrono::duration<int64_t, std::nano>(
static_cast<int64_t>(timeout * 1e9));
#else
auto timeout_time = std::chrono::steady_clock::now() +
std::chrono::duration<double>(timeout);
#endif
*timed_out = false;
while (poller->poll_queue.empty()) {
if (poller->terminating) return infos;
if (poller->cancelling) {
// Note: this only works if there's a single thread calling this
// function for any particular poller, but that's the intended use.
poller->cancelling = false;
return infos;
}
if (timeout == 0) {
*timed_out = true;
return infos;
}
if (timeout < 0) {
poller->poll_cond.wait(lock);
} else {
auto cond_timed_out = poller->poll_cond.wait_until(lock, timeout_time);
if (cond_timed_out == std::cv_status::timeout) {
*timed_out = true;
return infos;
}
}
}
while (!poller->poll_queue.empty()) {
infos.emplace_back(std::move(poller->poll_queue.front()));
poller->poll_queue.pop();
}
return infos;
}
void CancelPoll(unsigned int poller_uid) {
std::shared_ptr<typename Thread::Poller> poller;
{
auto thr = m_owner.GetThread();
if (!thr) return;
if (poller_uid > thr->m_pollers.size()) return;
poller = thr->m_pollers[poller_uid];
if (!poller) return;
}
{
std::lock_guard<wpi::mutex> lock(poller->poll_mutex);
poller->cancelling = true;
}
poller->poll_cond.notify_one();
}
protected:
template <typename... Args>
void DoStart(Args&&... args) {
auto thr = m_owner.GetThread();
if (!thr) m_owner.Start(new Thread(std::forward<Args>(args)...));
}
template <typename... Args>
unsigned int DoAdd(Args&&... args) {
static_cast<Derived*>(this)->Start();
auto thr = m_owner.GetThread();
return thr->m_listeners.emplace_back(std::forward<Args>(args)...);
}
template <typename... Args>
void Send(unsigned int only_listener, Args&&... args) {
auto thr = m_owner.GetThread();
if (!thr || thr->m_listeners.empty()) return;
thr->m_queue.emplace(std::piecewise_construct,
std::make_tuple(only_listener),
std::forward_as_tuple(std::forward<Args>(args)...));
thr->m_cond.notify_one();
}
typename wpi::SafeThreadOwner<Thread>::Proxy GetThread() const {
return m_owner.GetThread();
}
private:
wpi::SafeThreadOwner<Thread> m_owner;
};
} // namespace nt
#endif // NTCORE_CALLBACKMANAGER_H_