8_sync
Store definition
ExampleSync1
// SPDX-FileCopyrightText: 2020-2023 Jochem Rutgers
//
// SPDX-License-Identifier: CC0-1.0
int32 i
double d
(bool) sync ExampleSync2
ExampleSync2
// SPDX-FileCopyrightText: 2020-2023 Jochem Rutgers
//
// SPDX-License-Identifier: CC0-1.0
uint8 u
bool b
Application
// SPDX-FileCopyrightText: 2020-2023 Jochem Rutgers
//
// SPDX-License-Identifier: CC0-1.0
/*!
* \file
* \brief Example with multiple stores to be synced between multiple nodes.
*
* You can build any topology you want, but as an example with two parties:
*
* - Run first instance: `8_sync -i inst1 -d ipc:///tmp/8_sync_pipe -p 2222`
* - Run second instance: `8_sync -i inst2 -u ipc:///tmp/8_sync_pipe -p 2223`
* - Run a debugger for first instance: `python3 -m libstored.gui -p 2222`
* - Run a debugger for second instance: `python3 -m libstored.gui -p 2223`
* - Enable tracing on all variables. You will notice that when you change a
* value within /ExampleSync1, it will be synchronized immediately.
* Changes within /ExampleSync2 are only synchronized once you write to
* /ExampleSync1/sync ExampleSync2.
*/
#include "ExampleSync1.h"
#include "ExampleSync2.h"
#include <stdlib.h>
#include <stored>
#include <string.h>
#include "getopt_mini.h"
static stored::Synchronizer synchronizer;
class ExampleSync2
: public STORE_T(ExampleSync2, stored::Synchronizable, stored::ExampleSync2Base) {
STORE_CLASS(ExampleSync2, stored::Synchronizable, stored::ExampleSync2Base)
public:
ExampleSync2() is_default
};
static ExampleSync2 store2;
class ExampleSync1
: public STORE_T(ExampleSync1, stored::Synchronizable, stored::ExampleSync1Base) {
STORE_CLASS(ExampleSync1, stored::Synchronizable, stored::ExampleSync1Base)
public:
ExampleSync1() is_default
void __sync_ExampleSync2(bool set, bool& value)
{
if(set) {
printf("Triggered synchronization of store2\n");
synchronizer.process(store2);
} else
value = false;
}
};
static ExampleSync1 store1;
int main(int argc, char** argv)
{
int ret = 0;
stored::Debugger debugger("8_sync");
debugger.map(store1);
debugger.map(store2);
synchronizer.map(store1);
synchronizer.map(store2);
std::list<stored::ProtocolLayer*> otherLayers;
std::list<stored::SyncZmqLayer*> connections;
int debug_port = stored::DebugZmqLayer::DefaultPort;
bool verbose = false;
int c;
// flawfinder: ignore
while(ret == 0 && (c = getopt(argc, argv, "i:d:u:p:v")) != -1)
switch(c) {
case 'i':
printf("This is %s\n", optarg);
debugger.setIdentification(optarg);
break;
case 'p':
// flawfinder: ignore
debug_port = atoi(optarg);
if(debug_port == 0 || debug_port >= 0x10000) {
printf("Invalid port '%s'\n", optarg);
ret = 1;
}
break;
case 'v':
printf("Enable verbose output\n");
verbose = true;
break;
case 'd': {
printf("Listen at %s for downstream sync\n", optarg);
stored::SyncZmqLayer* z = new stored::SyncZmqLayer(nullptr, optarg, true);
connections.push_back(z);
if((errno = z->lastError())) {
printf("Cannot initialize ZMQ, got error %d; %s\n", errno,
zmq_strerror(errno));
ret = 1;
break;
}
stored::ProtocolLayer* l = z;
if(verbose) {
l = new stored::BufferLayer();
stored::PrintLayer* p = new stored::PrintLayer(stdout, optarg);
otherLayers.push_back(l);
otherLayers.push_back(p);
p->wrap(*l);
z->wrap(*p);
}
synchronizer.connect(*l);
break;
}
case 'u': {
printf("Connect to %s for upstream sync\n", optarg);
stored::SyncZmqLayer* z = new stored::SyncZmqLayer(nullptr, optarg, false);
connections.push_back(z);
if((errno = z->lastError())) {
printf("Cannot initialize ZMQ, got error %d; %s\n", errno,
zmq_strerror(errno));
ret = 1;
break;
}
stored::ProtocolLayer* l = z;
if(verbose) {
l = new stored::BufferLayer();
stored::PrintLayer* p = new stored::PrintLayer(stdout, optarg);
otherLayers.push_back(l);
otherLayers.push_back(p);
p->wrap(*l);
z->wrap(*p);
}
synchronizer.connect(*l);
synchronizer.syncFrom(store1, *l);
synchronizer.syncFrom(store2, *l);
break;
}
default:
printf("Usage: %s [-v] [-i <name>] [-p <port>] [-d <endpoint>|-u "
"<endpoint>]*\n",
argv[0]);
printf("where\n");
printf(" -d Listen for incoming 0MQ endpoint for downstream sync.\n");
printf(" -i Set debugger's identification name.\n");
printf(" -p Set debugger's port\n");
printf(" -u Connect to 0MQ endpoint for upstream sync.\n\n");
printf(" -v Verbose output of sync connections. Applies only to\n");
printf(" -u and -d options after -v.\n");
printf("Specify -i and -u as often as required.\n\n");
ret = 1;
}
stored::DebugZmqLayer debugLayer(nullptr, debug_port);
if((errno = debugLayer.lastError())) {
printf("Cannot initialize ZMQ for debugging, got error %d; %s\n", errno,
zmq_strerror(errno));
ret = 1;
}
debugLayer.wrap(debugger);
std::vector<zmq_pollitem_t> fds;
fds.reserve(connections.size() + 1 /* debugger */);
for(std::list<stored::SyncZmqLayer*>::iterator it = connections.begin();
it != connections.end(); ++it) {
zmq_pollitem_t fd = {};
fd.socket = (*it)->socket();
fd.events = ZMQ_POLLIN;
fds.push_back(fd);
}
{
zmq_pollitem_t fd = {};
fd.socket = debugLayer.socket();
fd.events = ZMQ_POLLIN;
fds.push_back(fd);
}
while(ret == 0) {
// Go sync store1 on all connections.
synchronizer.process(store1);
// Wait for input...
int cnt = zmq_poll(&fds.front(), (int)fds.size(), -1);
switch(cnt) {
case -1:
printf("Poll returned error %d; %s\n", errno, zmq_strerror(errno));
goto done;
case 0:
// Nothing to be done.
continue;
default:;
}
// Look for connection that has activity.
size_t i = 0;
int res = 0;
for(std::list<stored::SyncZmqLayer*>::iterator it = connections.begin();
it != connections.end() && cnt; ++it, i++) {
if(fds[i].revents & ZMQ_POLLIN) {
cnt--;
if((res = (*it)->recv())) {
printf("Sync socket recv error %d; %s\n", res,
zmq_strerror(res));
goto done;
}
}
}
if(cnt) {
// Must be the debugger socket, which was last in line.
if(fds[fds.size() - 1].revents & ZMQ_POLLIN) {
cnt--;
if((res = debugLayer.recv())) {
printf("Debugger socket recv error %d; %s\n", res,
zmq_strerror(res));
goto done;
}
}
}
stored_assert(cnt == 0);
}
done:
for(std::list<stored::SyncZmqLayer*>::iterator it = connections.begin();
it != connections.end(); ++it) {
synchronizer.disconnect(**it);
delete *it;
}
for(std::list<stored::ProtocolLayer*>::iterator it = otherLayers.begin();
it != otherLayers.end(); ++it)
delete *it;
return ret;
}
Store reference
-
template<typename Base_, typename Implementation_>
class ExampleSync1Objects All ExampleSync1Base’s objects.
Subclassed by stored::ExampleSync1Base< ExampleSync1 >
Public Members
-
impl::StoreVariable<Base, Implementation, double, 0u, 8> d
d
-
impl::StoreVariable<Base, Implementation, int32_t, 8u, 4> i
i
-
impl::StoreFunction<Base, Implementation, ExampleSync1FunctionMap, 1u> sync_ExampleSync2
sync ExampleSync2
-
impl::StoreVariable<Base, Implementation, double, 0u, 8> d
-
template<typename Implementation_>
class ExampleSync1Base : public stored::ExampleSync1Objects<ExampleSync1Base<Implementation_>, Implementation_> Base class with default interface of all ExampleSync1 implementations.
Although there are no virtual functions in the base class, subclasses can override them. The (lowest) subclass must pass the
Implementation_
template paramater to its base, such that all calls from the base class can be directed to the proper overridden implementation.The base class cannot be instantiated. If a default implementation is required, which does not have side effects to functions, instantiate stored::ExampleSync1. This class contains all data of all variables, so it can be large. So, be aware when instantiating it on the stack. Heap is fine. Static allocations is fine too, as the constructor and destructor are trivial.
To inherit the base class, you can use the following template:
class ExampleSync1 : public stored::store<ExampleSync1, ExampleSync1Base>::type { STORE_CLASS(ExampleSync1, ExampleSync1Base) public: // Your class implementation, such as: ExampleSync1() is_default // ... };
Some compilers or tools may get confused by the inheritance using
stored::store
orstored::store_t
. Alternatively, useSTORE_T(...)
instead, providing the template parameters ofstored::store
as macro arguments.See also
stored::ExampleSync1
See also
stored::ExampleSync1Data
Public Types
-
enum [anonymous]
Values:
-
enumerator ObjectCount
Number of objects in the store.
-
enumerator VariableCount
Number of variables in the store.
-
enumerator FunctionCount
Number of functions in the store.
-
enumerator BufferSize
Buffer size.
-
enumerator ObjectCount
-
typedef Implementation_ Implementation
Type of the actual implementation, which is the (lowest) subclass.
-
typedef uintptr_t Key
Type of a key.
See also
-
typedef Map<String::type, Variant<Implementation>>::type ObjectMap
Map as generated by map().
-
typedef ExampleSync1Objects<ExampleSync1Base, Implementation_> Objects
-
typedef ExampleSync1Base root
We are the root, as used by
STORE_CLASS
.
-
typedef ExampleSync1Base self
Define
self
forstored::store
.
Public Functions
-
inline ~ExampleSync1Base()
-
void __sync_ExampleSync2(bool set, bool &value)
Callback for sync ExampleSync2.
-
inline Key bufferToKey(void const *buffer) const noexcept
Converts a variable’s buffer to a key.
A key is unique for all variables of the same store, but identical for the same variables across different instances of the same store class. Therefore, the key can be used to synchronize between instances of the same store. A key does not contain meta data, such as type or length. It is up to the synchronization library to make sure that these properties are handled well.
For synchronization, when hookEntryX() or hookEntryRO() is invoked, one can compute the key of the object that is accessed. The key can be used, for example, in a key-to-Variant map. When data arrives from another party, the key can be used to find the proper Variant in the map.
This way, data exchange is type-safe, as the Variant can check if the data format matches the expected type. However, one cannot process data if the key is not set yet in the map.
-
inline Type::type bufferToType(void const *buffer) noexcept
Return the type of the variable, given its buffer.
-
inline Variant<Implementation> find(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept
Finds an object with the given name.
- Returns:
the object, or an invalid stored::Variant if not found.
-
template<typename T>
inline Function<T, Implementation> function(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a function with the given name.
The function, when it exists, must have the given (fixed) type.
-
inline Implementation const &implementation() const noexcept
Returns the reference to the implementation.
-
inline Implementation &implementation() noexcept
Returns the reference to the implementation.
-
template<typename F>
inline void list(F &&f) noexcept Calls a callback for every object in the longDirectory().
See also
-
template<typename F>
inline void list(F f, void *arg, char const *prefix, String::type *nameBuffer) noexcept Calls a callback for every object in the longDirectory().
See also
-
template<typename F>
inline void list(F f, void *arg, char const *prefix = nullptr) noexcept Calls a callback for every object in the longDirectory().
See also
-
inline uint8_t const *longDirectory() const noexcept
Retuns the long directory.
When not available, the short directory is returned.
-
inline ObjectMap map(char const *prefix = nullptr)
Create a name to Variant map for the store.
Generating the map may be expensive and the result is not cached.
-
inline char const *name() const noexcept
Returns the name of store, which can be used as prefix for stored::Debugger.
-
inline uint8_t const *shortDirectory() const noexcept
Returns the short directory.
-
template<typename T>
inline Variable<T, Implementation> variable(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a variable with the given name.
The variable, when it exists, must have the given (fixed) type.
Public Members
-
impl::StoreVariable<Base, Implementation, double, 0u, 8> d
d
-
impl::StoreVariable<Base, Implementation, int32_t, 8u, 4> i
i
-
impl::StoreFunction<Base, Implementation, ExampleSync1FunctionMap, 1u> sync_ExampleSync2
sync ExampleSync2
Public Static Functions
-
template<typename T>
static inline constexpr FreeFunction<T, Implementation> freeFunction(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a function with the given name.
The function, when it exists, must have the given (fixed) type. It is returned as a free function; it is not bound yet to a specific store instance. This function is constexpr for C++14.
-
template<typename T>
static inline constexpr FreeVariable<T, Implementation> freeVariable(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a variable with the given name.
The variable, when it exists, must have the given (fixed) type. It is returned as a free variable; it is not bound yet to a specific store instance. This function is constexpr for C++14.
-
static inline constexpr char const *hash() noexcept
Returns a unique hash of the store.
Friends
- friend class impl::StoreFunction
- friend class impl::StoreVariable
- friend class impl::StoreVariantF
- friend class impl::StoreVariantV
- friend class stored::FreeVariable
- friend class stored::Variant< void >
-
enum [anonymous]
-
class ExampleSync1 : public stored::Synchronizable<stored::ExampleSync1Base<ExampleSync1>>
-
template<typename Base_, typename Implementation_>
class ExampleSync2Objects All ExampleSync2Base’s objects.
Subclassed by stored::ExampleSync2Base< ExampleSync2 >
Public Members
-
impl::StoreVariable<Base, Implementation, bool, 1u, 1> b
b
-
impl::StoreVariable<Base, Implementation, uint8_t, 0u, 1> u
u
-
impl::StoreVariable<Base, Implementation, bool, 1u, 1> b
-
template<typename Implementation_>
class ExampleSync2Base : public stored::ExampleSync2Objects<ExampleSync2Base<Implementation_>, Implementation_> Base class with default interface of all ExampleSync2 implementations.
Although there are no virtual functions in the base class, subclasses can override them. The (lowest) subclass must pass the
Implementation_
template paramater to its base, such that all calls from the base class can be directed to the proper overridden implementation.The base class cannot be instantiated. If a default implementation is required, which does not have side effects to functions, instantiate stored::ExampleSync2. This class contains all data of all variables, so it can be large. So, be aware when instantiating it on the stack. Heap is fine. Static allocations is fine too, as the constructor and destructor are trivial.
To inherit the base class, you can use the following template:
class ExampleSync2 : public stored::store<ExampleSync2, ExampleSync2Base>::type { STORE_CLASS(ExampleSync2, ExampleSync2Base) public: // Your class implementation, such as: ExampleSync2() is_default // ... };
Some compilers or tools may get confused by the inheritance using
stored::store
orstored::store_t
. Alternatively, useSTORE_T(...)
instead, providing the template parameters ofstored::store
as macro arguments.See also
stored::ExampleSync2
See also
stored::ExampleSync2Data
Public Types
-
enum [anonymous]
Values:
-
enumerator ObjectCount
Number of objects in the store.
-
enumerator VariableCount
Number of variables in the store.
-
enumerator FunctionCount
Number of functions in the store.
-
enumerator BufferSize
Buffer size.
-
enumerator ObjectCount
-
typedef Implementation_ Implementation
Type of the actual implementation, which is the (lowest) subclass.
-
typedef uintptr_t Key
Type of a key.
See also
-
typedef Map<String::type, Variant<Implementation>>::type ObjectMap
Map as generated by map().
-
typedef ExampleSync2Objects<ExampleSync2Base, Implementation_> Objects
-
typedef ExampleSync2Base root
We are the root, as used by
STORE_CLASS
.
-
typedef ExampleSync2Base self
Define
self
forstored::store
.
Public Functions
-
inline ~ExampleSync2Base()
-
inline Key bufferToKey(void const *buffer) const noexcept
Converts a variable’s buffer to a key.
A key is unique for all variables of the same store, but identical for the same variables across different instances of the same store class. Therefore, the key can be used to synchronize between instances of the same store. A key does not contain meta data, such as type or length. It is up to the synchronization library to make sure that these properties are handled well.
For synchronization, when hookEntryX() or hookEntryRO() is invoked, one can compute the key of the object that is accessed. The key can be used, for example, in a key-to-Variant map. When data arrives from another party, the key can be used to find the proper Variant in the map.
This way, data exchange is type-safe, as the Variant can check if the data format matches the expected type. However, one cannot process data if the key is not set yet in the map.
-
inline Type::type bufferToType(void const *buffer) noexcept
Return the type of the variable, given its buffer.
-
inline Variant<Implementation> find(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept
Finds an object with the given name.
- Returns:
the object, or an invalid stored::Variant if not found.
-
template<typename T>
inline Function<T, Implementation> function(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a function with the given name.
The function, when it exists, must have the given (fixed) type.
-
inline Implementation const &implementation() const noexcept
Returns the reference to the implementation.
-
inline Implementation &implementation() noexcept
Returns the reference to the implementation.
-
template<typename F>
inline void list(F &&f) noexcept Calls a callback for every object in the longDirectory().
See also
-
template<typename F>
inline void list(F f, void *arg, char const *prefix, String::type *nameBuffer) noexcept Calls a callback for every object in the longDirectory().
See also
-
template<typename F>
inline void list(F f, void *arg, char const *prefix = nullptr) noexcept Calls a callback for every object in the longDirectory().
See also
-
inline uint8_t const *longDirectory() const noexcept
Retuns the long directory.
When not available, the short directory is returned.
-
inline ObjectMap map(char const *prefix = nullptr)
Create a name to Variant map for the store.
Generating the map may be expensive and the result is not cached.
-
inline char const *name() const noexcept
Returns the name of store, which can be used as prefix for stored::Debugger.
-
inline uint8_t const *shortDirectory() const noexcept
Returns the short directory.
-
template<typename T>
inline Variable<T, Implementation> variable(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a variable with the given name.
The variable, when it exists, must have the given (fixed) type.
Public Members
-
impl::StoreVariable<Base, Implementation, bool, 1u, 1> b
b
-
impl::StoreVariable<Base, Implementation, uint8_t, 0u, 1> u
u
Public Static Functions
-
template<typename T>
static inline constexpr FreeFunction<T, Implementation> freeFunction(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a function with the given name.
The function, when it exists, must have the given (fixed) type. It is returned as a free function; it is not bound yet to a specific store instance. This function is constexpr for C++14.
-
template<typename T>
static inline constexpr FreeVariable<T, Implementation> freeVariable(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept Finds a variable with the given name.
The variable, when it exists, must have the given (fixed) type. It is returned as a free variable; it is not bound yet to a specific store instance. This function is constexpr for C++14.
-
static inline constexpr char const *hash() noexcept
Returns a unique hash of the store.
Friends
- friend class impl::StoreFunction
- friend class impl::StoreVariable
- friend class impl::StoreVariantF
- friend class impl::StoreVariantV
- friend class stored::FreeVariable
- friend class stored::Variant< void >
-
enum [anonymous]
-
class ExampleSync2 : public stored::Synchronizable<stored::ExampleSync2Base<ExampleSync2>>
Public Functions
-
ExampleSync2() = default
-
ExampleSync2() = default