mirror of https://github.com/commaai/cereal.git
FakeSubSocket and FakePubSocket for IPC synchronization (#439)
* Implementation of FakeSubSocket and FakePubSocket using eventfd with support for one-in/one-out synchronization * Expose FakeEvent to Python * Add demo showcasing synchronization between processes * Fix linter errors * Expose more FakeEvent APIs in Python bindings * Add FakePoller implementation * Remove suffix from poll env vars * Set poller timeout to zero when events are enabled * Replace poll with ppoll. Add invalidation methods * Fix lint issues * Fix comment indent * Remove fake_demo * Remove FakePubSocket. Simpler FakePoller implementation. Ability to wait for multiple events * Rename FakeEvent to Event and move it to event.cc * Rename event purpose constants in py * Add support for timeout in wait methods * Add tests for events and fake sockets * Fix lint errors * Add zmq sleeps * Temporarly disable TestFakeSockets on ZMQ * Add exception type specifiers to test_fake * Event Manager implementation * Fix fake sockets tests * Update EventManager API * Add test for enable/disable * Add tests for cereal prefix * Remove EventPurpose from python bindings * Fix lint issues * event_state_shm_mmap implementation shared by EventManager and FakeSubSocket * Rename EventManager to SocketEventHandle * More renames
This commit is contained in:
parent
0b5e05013c
commit
4e063ca166
|
@ -26,8 +26,10 @@ services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_di
|
|||
|
||||
messaging_objects = env.SharedObject([
|
||||
'messaging/messaging.cc',
|
||||
'messaging/event.cc',
|
||||
'messaging/impl_zmq.cc',
|
||||
'messaging/impl_msgq.cc',
|
||||
'messaging/impl_fake.cc',
|
||||
'messaging/msgq.cc',
|
||||
'messaging/socketmaster.cc',
|
||||
])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# must be build with scons
|
||||
from .messaging_pyx import Context, Poller, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
from .messaging_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event # pylint: disable=no-name-in-module, import-error
|
||||
from .messaging_pyx import MultiplePublishersError, MessagingError # pylint: disable=no-name-in-module, import-error
|
||||
import os
|
||||
import capnp
|
||||
|
@ -12,6 +12,11 @@ from cereal.services import service_list
|
|||
|
||||
assert MultiplePublishersError
|
||||
assert MessagingError
|
||||
assert toggle_fake_events
|
||||
assert set_fake_prefix
|
||||
assert get_fake_prefix
|
||||
assert delete_fake_prefix
|
||||
assert wait_for_one_event
|
||||
|
||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||
AVG_FREQ_HISTORY = 100
|
||||
|
@ -27,9 +32,20 @@ except ImportError:
|
|||
|
||||
context = Context()
|
||||
|
||||
|
||||
def fake_event_handle(endpoint: str, identifier: Optional[str] = None, override: bool = True, enable: bool = False) -> SocketEventHandle:
|
||||
identifier = identifier or get_fake_prefix()
|
||||
handle = SocketEventHandle(endpoint, identifier, override)
|
||||
if override:
|
||||
handle.enabled = enable
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def log_from_bytes(dat: bytes) -> capnp.lib.capnp._DynamicStructReader:
|
||||
return log.Event.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT)
|
||||
|
||||
|
||||
def new_message(service: Optional[str] = None, size: Optional[int] = None) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
dat = log.Event.new_message()
|
||||
dat.logMonoTime = int(sec_since_boot() * 1e9)
|
||||
|
@ -41,11 +57,13 @@ def new_message(service: Optional[str] = None, size: Optional[int] = None) -> ca
|
|||
dat.init(service, size)
|
||||
return dat
|
||||
|
||||
|
||||
def pub_sock(endpoint: str) -> PubSocket:
|
||||
sock = PubSocket()
|
||||
sock.connect(context, endpoint)
|
||||
return sock
|
||||
|
||||
|
||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
||||
sock = SubSocket()
|
||||
|
@ -75,6 +93,7 @@ def drain_sock_raw(sock: SubSocket, wait_for_one: bool = False) -> List[bytes]:
|
|||
|
||||
return ret
|
||||
|
||||
|
||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Receive all message currently available on the queue"""
|
||||
ret: List[capnp.lib.capnp._DynamicStructReader] = []
|
||||
|
@ -114,18 +133,21 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._
|
|||
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one_retry(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader:
|
||||
"""Keep receiving until we get a message"""
|
||||
while True:
|
||||
|
@ -133,6 +155,7 @@ def recv_one_retry(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader:
|
|||
if dat is not None:
|
||||
return log_from_bytes(dat)
|
||||
|
||||
|
||||
class SubMaster:
|
||||
def __init__(self, services: List[str], poll: Optional[List[str]] = None,
|
||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
||||
|
@ -247,6 +270,7 @@ class SubMaster:
|
|||
and self.all_freq_ok(service_list=service_list) \
|
||||
and self.all_valid(service_list=service_list)
|
||||
|
||||
|
||||
class PubMaster:
|
||||
def __init__(self, services: List[str]):
|
||||
self.sock = {}
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "cereal/messaging/event.h"
|
||||
|
||||
void event_state_shm_mmap(std::string endpoint, std::string identifier, char **shm_mem, std::string *shm_path) {
|
||||
const char* op_prefix = std::getenv("OPENPILOT_PREFIX");
|
||||
|
||||
std::string full_path = "/dev/shm/";
|
||||
if (op_prefix) {
|
||||
full_path += std::string(op_prefix) + "/";
|
||||
}
|
||||
full_path += CEREAL_EVENTS_PREFIX + "/";
|
||||
if (identifier.size() > 0) {
|
||||
full_path += identifier + "/";
|
||||
}
|
||||
std::filesystem::create_directories(full_path);
|
||||
full_path += endpoint;
|
||||
|
||||
int shm_fd = open(full_path.c_str(), O_RDWR | O_CREAT, 0664);
|
||||
if (shm_fd < 0) {
|
||||
throw std::runtime_error("Could not open shared memory file.");
|
||||
}
|
||||
|
||||
int rc = ftruncate(shm_fd, sizeof(EventState));
|
||||
if (rc < 0){
|
||||
close(shm_fd);
|
||||
throw std::runtime_error("Could not truncate shared memory file.");
|
||||
}
|
||||
|
||||
char * mem = (char*)mmap(NULL, sizeof(EventState), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
|
||||
close(shm_fd);
|
||||
if (mem == nullptr) {
|
||||
throw std::runtime_error("Could not map shared memory file.");
|
||||
}
|
||||
|
||||
if (shm_mem != nullptr)
|
||||
*shm_mem = mem;
|
||||
if (shm_path != nullptr)
|
||||
*shm_path = full_path;
|
||||
}
|
||||
|
||||
SocketEventHandle::SocketEventHandle(std::string endpoint, std::string identifier, bool override) {
|
||||
char *mem;
|
||||
event_state_shm_mmap(endpoint, identifier, &mem, &this->shm_path);
|
||||
|
||||
this->state = (EventState*)mem;
|
||||
if (override) {
|
||||
this->state->fds[0] = eventfd(0, EFD_NONBLOCK);
|
||||
this->state->fds[1] = eventfd(0, EFD_NONBLOCK);
|
||||
}
|
||||
}
|
||||
|
||||
SocketEventHandle::~SocketEventHandle() {
|
||||
close(this->state->fds[0]);
|
||||
close(this->state->fds[1]);
|
||||
munmap(this->state, sizeof(EventState));
|
||||
unlink(this->shm_path.c_str());
|
||||
}
|
||||
|
||||
bool SocketEventHandle::is_enabled() {
|
||||
return this->state->enabled;
|
||||
}
|
||||
|
||||
void SocketEventHandle::set_enabled(bool enabled) {
|
||||
this->state->enabled = enabled;
|
||||
}
|
||||
|
||||
Event SocketEventHandle::recv_called() {
|
||||
return Event(this->state->fds[0]);
|
||||
}
|
||||
|
||||
Event SocketEventHandle::recv_ready() {
|
||||
return Event(this->state->fds[1]);
|
||||
}
|
||||
|
||||
void SocketEventHandle::toggle_fake_events(bool enabled) {
|
||||
if (enabled)
|
||||
setenv("CEREAL_FAKE", "1", true);
|
||||
else
|
||||
unsetenv("CEREAL_FAKE");
|
||||
}
|
||||
|
||||
void SocketEventHandle::set_fake_prefix(std::string prefix) {
|
||||
if (prefix.size() == 0) {
|
||||
unsetenv("CEREAL_FAKE_PREFIX");
|
||||
} else {
|
||||
setenv("CEREAL_FAKE_PREFIX", prefix.c_str(), true);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SocketEventHandle::fake_prefix() {
|
||||
const char* prefix = std::getenv("CEREAL_FAKE_PREFIX");
|
||||
if (prefix == nullptr) {
|
||||
return "";
|
||||
} else {
|
||||
return std::string(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
Event::Event(int fd): event_fd(fd) {}
|
||||
|
||||
void Event::set() const {
|
||||
throw_if_invalid();
|
||||
|
||||
uint64_t val = 1;
|
||||
size_t count = write(this->event_fd, &val, sizeof(uint64_t));
|
||||
assert(count == sizeof(uint64_t));
|
||||
}
|
||||
|
||||
int Event::clear() const {
|
||||
throw_if_invalid();
|
||||
|
||||
uint64_t val = 0;
|
||||
// read the eventfd to clear it
|
||||
read(this->event_fd, &val, sizeof(uint64_t));
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
void Event::wait(int timeout_sec) const {
|
||||
throw_if_invalid();
|
||||
|
||||
int event_count;
|
||||
struct pollfd fds = { this->event_fd, POLLIN, 0 };
|
||||
|
||||
struct timespec timeout = { timeout_sec, 0 };
|
||||
|
||||
sigset_t signals;
|
||||
sigfillset(&signals);
|
||||
sigdelset(&signals, SIGALRM);
|
||||
sigdelset(&signals, SIGINT);
|
||||
sigdelset(&signals, SIGTERM);
|
||||
sigdelset(&signals, SIGQUIT);
|
||||
|
||||
event_count = ppoll(&fds, 1, timeout_sec < 0 ? nullptr : &timeout, &signals);
|
||||
|
||||
if (event_count == 0) {
|
||||
throw std::runtime_error("Event timed out pid: " + std::to_string(getpid()));
|
||||
} else if (event_count < 0) {
|
||||
throw std::runtime_error("Event poll failed, errno: " + std::to_string(errno) + " pid: " + std::to_string(getpid()));
|
||||
}
|
||||
}
|
||||
|
||||
bool Event::peek() const {
|
||||
throw_if_invalid();
|
||||
|
||||
int event_count;
|
||||
struct pollfd fds = { this->event_fd, POLLIN, 0 };
|
||||
|
||||
// poll with timeout zero to return status immediately
|
||||
event_count = poll(&fds, 1, 0);
|
||||
|
||||
return event_count != 0;
|
||||
}
|
||||
|
||||
bool Event::is_valid() const {
|
||||
return event_fd != -1;
|
||||
}
|
||||
|
||||
int Event::fd() const {
|
||||
return event_fd;
|
||||
}
|
||||
|
||||
int Event::wait_for_one(const std::vector<Event>& events, int timeout_sec) {
|
||||
struct pollfd fds[events.size()];
|
||||
for (size_t i = 0; i < events.size(); i++) {
|
||||
fds[i] = { events[i].fd(), POLLIN, 0 };
|
||||
}
|
||||
|
||||
struct timespec timeout = { timeout_sec, 0 };
|
||||
|
||||
sigset_t signals;
|
||||
sigfillset(&signals);
|
||||
sigdelset(&signals, SIGALRM);
|
||||
sigdelset(&signals, SIGINT);
|
||||
sigdelset(&signals, SIGTERM);
|
||||
sigdelset(&signals, SIGQUIT);
|
||||
|
||||
int event_count = ppoll(fds, events.size(), timeout_sec < 0 ? nullptr : &timeout, &signals);
|
||||
|
||||
if (event_count == 0) {
|
||||
throw std::runtime_error("Event timed out pid: " + std::to_string(getpid()));
|
||||
} else if (event_count < 0) {
|
||||
throw std::runtime_error("Event poll failed, errno: " + std::to_string(errno) + " pid: " + std::to_string(getpid()));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < events.size(); i++) {
|
||||
if (fds[i].revents & POLLIN) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Event poll failed, no events ready");
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define CEREAL_EVENTS_PREFIX std::string("cereal_events")
|
||||
|
||||
void event_state_shm_mmap(std::string endpoint, std::string identifier, char **shm_mem, std::string *shm_path);
|
||||
|
||||
enum EventPurpose {
|
||||
RECV_CALLED,
|
||||
RECV_READY
|
||||
};
|
||||
|
||||
struct EventState {
|
||||
int fds[2];
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
class Event {
|
||||
private:
|
||||
int event_fd = -1;
|
||||
|
||||
inline void throw_if_invalid() const {
|
||||
if (!this->is_valid()) {
|
||||
throw std::runtime_error("Event does not have valid file descriptor.");
|
||||
}
|
||||
}
|
||||
public:
|
||||
Event(int fd = -1);
|
||||
|
||||
void set() const;
|
||||
int clear() const;
|
||||
void wait(int timeout_sec = -1) const;
|
||||
bool peek() const;
|
||||
bool is_valid() const;
|
||||
int fd() const;
|
||||
|
||||
static int wait_for_one(const std::vector<Event>& events, int timeout_sec = -1);
|
||||
};
|
||||
|
||||
class SocketEventHandle {
|
||||
private:
|
||||
std::string shm_path;
|
||||
EventState* state;
|
||||
public:
|
||||
SocketEventHandle(std::string endpoint, std::string identifier = "", bool override = true);
|
||||
~SocketEventHandle();
|
||||
|
||||
bool is_enabled();
|
||||
void set_enabled(bool enabled);
|
||||
Event recv_called();
|
||||
Event recv_ready();
|
||||
|
||||
static void toggle_fake_events(bool enabled);
|
||||
static void set_fake_prefix(std::string prefix);
|
||||
static std::string fake_prefix();
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
#include "cereal/messaging/impl_fake.h"
|
||||
|
||||
void FakePoller::registerSocket(SubSocket *socket) {
|
||||
this->sockets.push_back(socket);
|
||||
}
|
||||
|
||||
std::vector<SubSocket*> FakePoller::poll(int timeout) {
|
||||
return this->sockets;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "cereal/messaging/messaging.h"
|
||||
#include "cereal/messaging/event.h"
|
||||
|
||||
template<typename TSubSocket>
|
||||
class FakeSubSocket: public TSubSocket {
|
||||
private:
|
||||
Event *recv_called = nullptr;
|
||||
Event *recv_ready = nullptr;
|
||||
EventState *state = nullptr;
|
||||
|
||||
public:
|
||||
FakeSubSocket(): TSubSocket() {}
|
||||
~FakeSubSocket() {
|
||||
delete recv_called;
|
||||
delete recv_ready;
|
||||
if (state != nullptr) {
|
||||
munmap(state, sizeof(EventState));
|
||||
}
|
||||
}
|
||||
|
||||
int connect(Context *context, std::string endpoint, std::string address, bool conflate=false, bool check_endpoint=true) override {
|
||||
const char* cereal_prefix = std::getenv("CEREAL_FAKE_PREFIX");
|
||||
|
||||
char* mem;
|
||||
std::string identifier = cereal_prefix != nullptr ? std::string(cereal_prefix) : "";
|
||||
event_state_shm_mmap(endpoint, identifier, &mem, nullptr);
|
||||
|
||||
this->state = (EventState*)mem;
|
||||
this->recv_called = new Event(state->fds[EventPurpose::RECV_CALLED]);
|
||||
this->recv_ready = new Event(state->fds[EventPurpose::RECV_READY]);
|
||||
|
||||
return TSubSocket::connect(context, endpoint, address, conflate, check_endpoint);
|
||||
}
|
||||
|
||||
Message *receive(bool non_blocking=false) override {
|
||||
if (this->state->enabled) {
|
||||
this->recv_called->set();
|
||||
this->recv_ready->wait();
|
||||
this->recv_ready->clear();
|
||||
}
|
||||
|
||||
return TSubSocket::receive(non_blocking);
|
||||
}
|
||||
};
|
||||
|
||||
class FakePoller: public Poller {
|
||||
private:
|
||||
std::vector<SubSocket*> sockets;
|
||||
|
||||
public:
|
||||
void registerSocket(SubSocket *socket) override;
|
||||
std::vector<SubSocket*> poll(int timeout) override;
|
||||
~FakePoller() {};
|
||||
};
|
|
@ -4,6 +4,7 @@
|
|||
#include "cereal/messaging/messaging.h"
|
||||
#include "cereal/messaging/impl_zmq.h"
|
||||
#include "cereal/messaging/impl_msgq.h"
|
||||
#include "cereal/messaging/impl_fake.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
const bool MUST_USE_ZMQ = true;
|
||||
|
@ -22,6 +23,11 @@ bool messaging_use_zmq(){
|
|||
return false;
|
||||
}
|
||||
|
||||
bool messaging_use_fake(){
|
||||
char* fake_enabled = std::getenv("CEREAL_FAKE");
|
||||
return fake_enabled != NULL;
|
||||
}
|
||||
|
||||
Context * Context::create(){
|
||||
Context * c;
|
||||
if (messaging_use_zmq()){
|
||||
|
@ -34,11 +40,20 @@ Context * Context::create(){
|
|||
|
||||
SubSocket * SubSocket::create(){
|
||||
SubSocket * s;
|
||||
if (messaging_use_zmq()){
|
||||
s = new ZMQSubSocket();
|
||||
if (messaging_use_fake()) {
|
||||
if (messaging_use_zmq()) {
|
||||
s = new FakeSubSocket<ZMQSubSocket>();
|
||||
} else {
|
||||
s = new FakeSubSocket<MSGQSubSocket>();
|
||||
}
|
||||
} else {
|
||||
s = new MSGQSubSocket();
|
||||
if (messaging_use_zmq()){
|
||||
s = new ZMQSubSocket();
|
||||
} else {
|
||||
s = new MSGQSubSocket();
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -61,6 +76,7 @@ PubSocket * PubSocket::create(){
|
|||
} else {
|
||||
s = new MSGQPubSocket();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -78,10 +94,14 @@ PubSocket * PubSocket::create(Context * context, std::string endpoint, bool chec
|
|||
|
||||
Poller * Poller::create(){
|
||||
Poller * p;
|
||||
if (messaging_use_zmq()){
|
||||
p = new ZMQPoller();
|
||||
if (messaging_use_fake()) {
|
||||
p = new FakePoller();
|
||||
} else {
|
||||
p = new MSGQPoller();
|
||||
if (messaging_use_zmq()){
|
||||
p = new ZMQPoller();
|
||||
} else {
|
||||
p = new MSGQPoller();
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,34 @@ from libcpp.vector cimport vector
|
|||
from libcpp cimport bool
|
||||
|
||||
|
||||
cdef extern from "cereal/messaging/impl_fake.h":
|
||||
cdef cppclass Event:
|
||||
@staticmethod
|
||||
int wait_for_one(vector[Event], int) except +
|
||||
|
||||
Event()
|
||||
Event(int)
|
||||
void set()
|
||||
int clear()
|
||||
void wait(int) except +
|
||||
bool peek()
|
||||
int fd()
|
||||
|
||||
cdef cppclass SocketEventHandle:
|
||||
@staticmethod
|
||||
void toggle_fake_events(bool)
|
||||
@staticmethod
|
||||
void set_fake_prefix(string)
|
||||
@staticmethod
|
||||
string fake_prefix()
|
||||
|
||||
SocketEventHandle(string, string, bool)
|
||||
bool is_enabled()
|
||||
void set_enabled(bool)
|
||||
Event recv_called()
|
||||
Event recv_ready()
|
||||
|
||||
|
||||
cdef extern from "cereal/messaging/messaging.h":
|
||||
cdef cppclass Context:
|
||||
@staticmethod
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
import sys
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp cimport bool
|
||||
from libc cimport errno
|
||||
from cython.operator import dereference
|
||||
|
||||
|
||||
from .messaging cimport Context as cppContext
|
||||
|
@ -12,6 +14,7 @@ from .messaging cimport SubSocket as cppSubSocket
|
|||
from .messaging cimport PubSocket as cppPubSocket
|
||||
from .messaging cimport Poller as cppPoller
|
||||
from .messaging cimport Message as cppMessage
|
||||
from .messaging cimport Event as cppEvent, SocketEventHandle as cppSocketEventHandle
|
||||
|
||||
|
||||
class MessagingError(Exception):
|
||||
|
@ -22,6 +25,91 @@ class MultiplePublishersError(MessagingError):
|
|||
pass
|
||||
|
||||
|
||||
def toggle_fake_events(bool enabled):
|
||||
cppSocketEventHandle.toggle_fake_events(enabled)
|
||||
|
||||
|
||||
def set_fake_prefix(string prefix):
|
||||
cppSocketEventHandle.set_fake_prefix(prefix)
|
||||
|
||||
|
||||
def get_fake_prefix():
|
||||
return cppSocketEventHandle.fake_prefix()
|
||||
|
||||
|
||||
def delete_fake_prefix():
|
||||
cppSocketEventHandle.set_fake_prefix(b"")
|
||||
|
||||
|
||||
def wait_for_one_event(list events, int timeout=-1):
|
||||
cdef vector[cppEvent] items
|
||||
for event in events:
|
||||
items.push_back(dereference(<cppEvent*><size_t>event.ptr))
|
||||
return cppEvent.wait_for_one(items, timeout)
|
||||
|
||||
|
||||
cdef class Event:
|
||||
cdef cppEvent event;
|
||||
|
||||
def __cinit__(self):
|
||||
pass
|
||||
|
||||
cdef setEvent(self, cppEvent event):
|
||||
self.event = event
|
||||
|
||||
def set(self):
|
||||
self.event.set()
|
||||
|
||||
def clear(self):
|
||||
return self.event.clear()
|
||||
|
||||
def wait(self, int timeout=-1):
|
||||
self.event.wait(timeout)
|
||||
|
||||
def peek(self):
|
||||
return self.event.peek()
|
||||
|
||||
@property
|
||||
def fd(self):
|
||||
return self.event.fd()
|
||||
|
||||
@property
|
||||
def ptr(self):
|
||||
return <size_t><void*>&self.event
|
||||
|
||||
|
||||
cdef class SocketEventHandle:
|
||||
cdef cppSocketEventHandle * handle;
|
||||
|
||||
def __cinit__(self, string endpoint, string identifier, bool override):
|
||||
self.handle = new cppSocketEventHandle(endpoint, identifier, override)
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.handle
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self.handle.is_enabled()
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, bool value):
|
||||
self.handle.set_enabled(value)
|
||||
|
||||
@property
|
||||
def recv_called_event(self):
|
||||
e = Event()
|
||||
e.setEvent(self.handle.recv_called())
|
||||
|
||||
return e
|
||||
|
||||
@property
|
||||
def recv_ready_event(self):
|
||||
e = Event()
|
||||
e.setEvent(self.handle.recv_ready())
|
||||
|
||||
return e
|
||||
|
||||
|
||||
cdef class Context:
|
||||
cdef cppContext * context
|
||||
|
||||
|
@ -68,6 +156,7 @@ cdef class Poller:
|
|||
|
||||
return sockets
|
||||
|
||||
|
||||
cdef class SubSocket:
|
||||
cdef cppSubSocket * socket
|
||||
cdef bool is_owner
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
import os
|
||||
import unittest
|
||||
import multiprocessing
|
||||
from parameterized import parameterized_class
|
||||
|
||||
import cereal.messaging as messaging
|
||||
|
||||
WAIT_TIMEOUT = 5
|
||||
|
||||
|
||||
class TestEvents(unittest.TestCase):
|
||||
|
||||
def test_mutation(self):
|
||||
handle = messaging.fake_event_handle("carState")
|
||||
event = handle.recv_called_event
|
||||
|
||||
self.assertFalse(event.peek())
|
||||
event.set()
|
||||
self.assertTrue(event.peek())
|
||||
event.clear()
|
||||
self.assertFalse(event.peek())
|
||||
|
||||
del event
|
||||
|
||||
def test_wait(self):
|
||||
handle = messaging.fake_event_handle("carState")
|
||||
event = handle.recv_called_event
|
||||
|
||||
event.set()
|
||||
try:
|
||||
event.wait(WAIT_TIMEOUT)
|
||||
self.assertTrue(event.peek())
|
||||
except RuntimeError:
|
||||
self.fail("event.wait() timed out")
|
||||
|
||||
def test_wait_multiprocess(self):
|
||||
handle = messaging.fake_event_handle("carState")
|
||||
event = handle.recv_called_event
|
||||
|
||||
def set_event_run():
|
||||
event.set()
|
||||
|
||||
try:
|
||||
p = multiprocessing.Process(target=set_event_run)
|
||||
p.start()
|
||||
event.wait(WAIT_TIMEOUT)
|
||||
self.assertTrue(event.peek())
|
||||
except RuntimeError:
|
||||
self.fail("event.wait() timed out")
|
||||
|
||||
p.kill()
|
||||
|
||||
def test_wait_zero_timeout(self):
|
||||
handle = messaging.fake_event_handle("carState")
|
||||
event = handle.recv_called_event
|
||||
|
||||
try:
|
||||
event.wait(0)
|
||||
self.fail("event.wait() did not time out")
|
||||
except RuntimeError:
|
||||
self.assertFalse(event.peek())
|
||||
|
||||
|
||||
@unittest.skipIf("ZMQ" in os.environ, "FakeSockets not supported on ZMQ")
|
||||
@parameterized_class([{"prefix": None}, {"prefix": "test"}])
|
||||
class TestFakeSockets(unittest.TestCase):
|
||||
prefix = None
|
||||
|
||||
def setUp(self):
|
||||
messaging.toggle_fake_events(True)
|
||||
if self.prefix is not None:
|
||||
messaging.set_fake_prefix(self.prefix)
|
||||
else:
|
||||
messaging.delete_fake_prefix()
|
||||
|
||||
def tearDown(self):
|
||||
messaging.toggle_fake_events(False)
|
||||
messaging.delete_fake_prefix()
|
||||
|
||||
def test_event_handle_init(self):
|
||||
handle = messaging.fake_event_handle("controlsState", override=True)
|
||||
|
||||
self.assertFalse(handle.enabled)
|
||||
self.assertGreaterEqual(handle.recv_called_event.fd, 0)
|
||||
self.assertGreaterEqual(handle.recv_ready_event.fd, 0)
|
||||
|
||||
def test_non_managed_socket_state(self):
|
||||
# non managed socket should have zero state
|
||||
_ = messaging.pub_sock("ubloxGnss")
|
||||
|
||||
handle = messaging.fake_event_handle("ubloxGnss", override=False)
|
||||
|
||||
self.assertFalse(handle.enabled)
|
||||
self.assertEqual(handle.recv_called_event.fd, 0)
|
||||
self.assertEqual(handle.recv_ready_event.fd, 0)
|
||||
|
||||
def test_managed_socket_state(self):
|
||||
# managed socket should not change anything about the state
|
||||
handle = messaging.fake_event_handle("ubloxGnss")
|
||||
handle.enabled = True
|
||||
|
||||
expected_enabled = handle.enabled
|
||||
expected_recv_called_fd = handle.recv_called_event.fd
|
||||
expected_recv_ready_fd = handle.recv_ready_event.fd
|
||||
|
||||
_ = messaging.pub_sock("ubloxGnss")
|
||||
|
||||
self.assertEqual(handle.enabled, expected_enabled)
|
||||
self.assertEqual(handle.recv_called_event.fd, expected_recv_called_fd)
|
||||
self.assertEqual(handle.recv_ready_event.fd, expected_recv_ready_fd)
|
||||
|
||||
def test_sockets_enable_disable(self):
|
||||
carState_handle = messaging.fake_event_handle("ubloxGnss", enable=True)
|
||||
recv_called = carState_handle.recv_called_event
|
||||
recv_ready = carState_handle.recv_ready_event
|
||||
|
||||
pub_sock = messaging.pub_sock("ubloxGnss")
|
||||
sub_sock = messaging.sub_sock("ubloxGnss")
|
||||
|
||||
try:
|
||||
carState_handle.enabled = True
|
||||
recv_ready.set()
|
||||
pub_sock.send(b"test")
|
||||
_ = sub_sock.receive()
|
||||
self.assertTrue(recv_called.peek())
|
||||
recv_called.clear()
|
||||
|
||||
carState_handle.enabled = False
|
||||
recv_ready.set()
|
||||
pub_sock.send(b"test")
|
||||
_ = sub_sock.receive()
|
||||
self.assertFalse(recv_called.peek())
|
||||
except RuntimeError:
|
||||
self.fail("event.wait() timed out")
|
||||
|
||||
def test_synced_pub_sub(self):
|
||||
def daemon_repub_process_run():
|
||||
pub_sock = messaging.pub_sock("ubloxGnss")
|
||||
sub_sock = messaging.sub_sock("carState")
|
||||
|
||||
frame = -1
|
||||
while True:
|
||||
frame += 1
|
||||
msg = sub_sock.receive(non_blocking=True)
|
||||
if msg is None:
|
||||
print("none received")
|
||||
continue
|
||||
|
||||
bts = frame.to_bytes(8, 'little')
|
||||
pub_sock.send(bts)
|
||||
|
||||
carState_handle = messaging.fake_event_handle("carState", enable=True)
|
||||
recv_called = carState_handle.recv_called_event
|
||||
recv_ready = carState_handle.recv_ready_event
|
||||
|
||||
p = multiprocessing.Process(target=daemon_repub_process_run)
|
||||
p.start()
|
||||
|
||||
pub_sock = messaging.pub_sock("carState")
|
||||
sub_sock = messaging.sub_sock("ubloxGnss")
|
||||
|
||||
try:
|
||||
for i in range(10):
|
||||
recv_called.wait(WAIT_TIMEOUT)
|
||||
recv_called.clear()
|
||||
|
||||
if i == 0:
|
||||
sub_sock.receive(non_blocking=True)
|
||||
|
||||
bts = i.to_bytes(8, 'little')
|
||||
pub_sock.send(bts)
|
||||
|
||||
recv_ready.set()
|
||||
recv_called.wait(WAIT_TIMEOUT)
|
||||
|
||||
msg = sub_sock.receive(non_blocking=True)
|
||||
self.assertIsNotNone(msg)
|
||||
self.assertEqual(len(msg), 8)
|
||||
|
||||
frame = int.from_bytes(msg, 'little')
|
||||
self.assertEqual(frame, i)
|
||||
except RuntimeError:
|
||||
self.fail("event.wait() timed out")
|
||||
finally:
|
||||
p.kill()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue