mirror of
https://github.com/zeromq/libzmq.git
synced 2024-12-12 10:33:52 +01:00
Problem: no unittests for mtrie
Solution: made mtrie generic (to remove complex dependency on pipe_t), and added some unit tests
This commit is contained in:
parent
9fc3692e3f
commit
a9712c0bf2
@ -611,6 +611,8 @@ set (cxx-sources
|
||||
fd.hpp
|
||||
fq.hpp
|
||||
gather.hpp
|
||||
generic_mtrie.hpp
|
||||
generic_mtrie_impl.hpp
|
||||
gssapi_client.hpp
|
||||
gssapi_mechanism_base.hpp
|
||||
gssapi_server.hpp
|
||||
|
11
Makefile.am
11
Makefile.am
@ -64,6 +64,8 @@ src_libzmq_la_SOURCES = \
|
||||
src/fq.hpp \
|
||||
src/gather.cpp \
|
||||
src/gather.hpp \
|
||||
src/generic_mtrie.hpp \
|
||||
src/generic_mtrie_impl.hpp \
|
||||
src/gssapi_mechanism_base.cpp \
|
||||
src/gssapi_mechanism_base.hpp \
|
||||
src/gssapi_client.cpp \
|
||||
@ -865,7 +867,8 @@ if ENABLE_STATIC
|
||||
# unit tests - these include individual source files and test the internal functions
|
||||
test_apps += \
|
||||
unittests/unittest_poller \
|
||||
unittests/unittest_ypipe
|
||||
unittests/unittest_ypipe \
|
||||
unittests/unittest_mtrie
|
||||
|
||||
unittests_unittest_poller_SOURCES = unittests/unittest_poller.cpp
|
||||
unittests_unittest_poller_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS}
|
||||
@ -878,6 +881,12 @@ unittests_unittest_ypipe_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS}
|
||||
unittests_unittest_ypipe_LDADD = $(top_builddir)/src/.libs/libzmq.a \
|
||||
${src_libzmq_la_LIBADD} \
|
||||
${UNITY_LIBS}
|
||||
|
||||
unittests_unittest_mtrie_SOURCES = unittests/unittest_mtrie.cpp
|
||||
unittests_unittest_mtrie_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS}
|
||||
unittests_unittest_mtrie_LDADD = $(top_builddir)/src/.libs/libzmq.a \
|
||||
${src_libzmq_la_LIBADD} \
|
||||
${UNITY_LIBS}
|
||||
endif
|
||||
|
||||
check_PROGRAMS = ${test_apps}
|
||||
|
104
src/generic_mtrie.hpp
Normal file
104
src/generic_mtrie.hpp
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
Copyright (c) 2018 Contributors as noted in the AUTHORS file
|
||||
|
||||
This file is part of libzmq, the ZeroMQ core engine in C++.
|
||||
|
||||
libzmq is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License (LGPL) as published
|
||||
by the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
As a special exception, the Contributors give you permission to link
|
||||
this library with independent modules to produce an executable,
|
||||
regardless of the license terms of these independent modules, and to
|
||||
copy and distribute the resulting executable under terms of your choice,
|
||||
provided that you also meet, for each linked independent module, the
|
||||
terms and conditions of the license of that module. An independent
|
||||
module is a module which is not derived from or based on this library.
|
||||
If you modify this library, you must extend this exception to your
|
||||
version of the library.
|
||||
|
||||
libzmq is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ZMQ_GENERIC_MTRIE_HPP_INCLUDED__
|
||||
#define __ZMQ_GENERIC_MTRIE_HPP_INCLUDED__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <set>
|
||||
|
||||
#include "stdint.hpp"
|
||||
|
||||
namespace zmq
|
||||
{
|
||||
// Multi-trie. Each node in the trie is a set of pointers to pipes.
|
||||
template <typename T> class generic_mtrie_t
|
||||
{
|
||||
public:
|
||||
typedef T value_t;
|
||||
typedef const unsigned char *prefix_t;
|
||||
|
||||
generic_mtrie_t ();
|
||||
~generic_mtrie_t ();
|
||||
|
||||
// Add key to the trie. Returns true if it's a new subscription
|
||||
// rather than a duplicate.
|
||||
bool add (prefix_t prefix_, size_t size_, value_t *pipe_);
|
||||
|
||||
// Remove all subscriptions for a specific peer from the trie.
|
||||
// The call_on_uniq_ flag controls if the callback is invoked
|
||||
// when there are no subscriptions left on some topics or on
|
||||
// every removal.
|
||||
void
|
||||
rm (value_t *pipe_,
|
||||
void (*func_) (const unsigned char *data_, size_t size_, void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_);
|
||||
|
||||
// Remove specific subscription from the trie. Return true is it was
|
||||
// actually removed rather than de-duplicated.
|
||||
bool rm (prefix_t prefix_, size_t size_, value_t *pipe_);
|
||||
|
||||
// Signal all the matching pipes.
|
||||
void match (prefix_t data_,
|
||||
size_t size_,
|
||||
void (*func_) (value_t *pipe_, void *arg_),
|
||||
void *arg_);
|
||||
|
||||
private:
|
||||
bool add_helper (prefix_t prefix_, size_t size_, value_t *pipe_);
|
||||
void rm_helper (value_t *pipe_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (prefix_t data_, size_t size_, void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_);
|
||||
bool rm_helper (prefix_t prefix_, size_t size_, value_t *pipe_);
|
||||
bool is_redundant () const;
|
||||
|
||||
typedef std::set<value_t *> pipes_t;
|
||||
pipes_t *pipes;
|
||||
|
||||
unsigned char min;
|
||||
unsigned short count;
|
||||
unsigned short live_nodes;
|
||||
union
|
||||
{
|
||||
class generic_mtrie_t<value_t> *node;
|
||||
class generic_mtrie_t<value_t> **table;
|
||||
} next;
|
||||
|
||||
generic_mtrie_t (const generic_mtrie_t<value_t> &);
|
||||
const generic_mtrie_t<value_t> &
|
||||
operator= (const generic_mtrie_t<value_t> &);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
450
src/generic_mtrie_impl.hpp
Normal file
450
src/generic_mtrie_impl.hpp
Normal file
@ -0,0 +1,450 @@
|
||||
/*
|
||||
Copyright (c) 2018 Contributors as noted in the AUTHORS file
|
||||
|
||||
This file is part of libzmq, the ZeroMQ core engine in C++.
|
||||
|
||||
libzmq is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License (LGPL) as published
|
||||
by the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
As a special exception, the Contributors give you permission to link
|
||||
this library with independent modules to produce an executable,
|
||||
regardless of the license terms of these independent modules, and to
|
||||
copy and distribute the resulting executable under terms of your choice,
|
||||
provided that you also meet, for each linked independent module, the
|
||||
terms and conditions of the license of that module. An independent
|
||||
module is a module which is not derived from or based on this library.
|
||||
If you modify this library, you must extend this exception to your
|
||||
version of the library.
|
||||
|
||||
libzmq is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ZMQ_GENERIC_MTRIE_IMPL_HPP_INCLUDED__
|
||||
#define __ZMQ_GENERIC_MTRIE_IMPL_HPP_INCLUDED__
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <new>
|
||||
#include <algorithm>
|
||||
|
||||
#include "err.hpp"
|
||||
#include "pipe.hpp"
|
||||
#include "macros.hpp"
|
||||
#include "generic_mtrie.hpp"
|
||||
|
||||
template <typename T>
|
||||
zmq::generic_mtrie_t<T>::generic_mtrie_t () :
|
||||
pipes (0),
|
||||
min (0),
|
||||
count (0),
|
||||
live_nodes (0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T> zmq::generic_mtrie_t<T>::~generic_mtrie_t ()
|
||||
{
|
||||
LIBZMQ_DELETE (pipes);
|
||||
|
||||
if (count == 1) {
|
||||
zmq_assert (next.node);
|
||||
LIBZMQ_DELETE (next.node);
|
||||
} else if (count > 1) {
|
||||
for (unsigned short i = 0; i != count; ++i) {
|
||||
LIBZMQ_DELETE (next.table[i]);
|
||||
}
|
||||
free (next.table);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool zmq::generic_mtrie_t<T>::add (prefix_t prefix_,
|
||||
size_t size_,
|
||||
value_t *pipe_)
|
||||
{
|
||||
return add_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool zmq::generic_mtrie_t<T>::add_helper (prefix_t prefix_,
|
||||
size_t size_,
|
||||
value_t *pipe_)
|
||||
{
|
||||
// We are at the node corresponding to the prefix. We are done.
|
||||
if (!size_) {
|
||||
bool result = !pipes;
|
||||
if (!pipes) {
|
||||
pipes = new (std::nothrow) pipes_t;
|
||||
alloc_assert (pipes);
|
||||
}
|
||||
pipes->insert (pipe_);
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (c < min || c >= min + count) {
|
||||
// The character is out of range of currently handled
|
||||
// characters. We have to extend the table.
|
||||
if (!count) {
|
||||
min = c;
|
||||
count = 1;
|
||||
next.node = NULL;
|
||||
} else if (count == 1) {
|
||||
unsigned char oldc = min;
|
||||
generic_mtrie_t *oldp = next.node;
|
||||
count = (min < c ? c - min : min - c) + 1;
|
||||
next.table =
|
||||
(generic_mtrie_t **) malloc (sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = 0; i != count; ++i)
|
||||
next.table[i] = 0;
|
||||
min = std::min (min, c);
|
||||
next.table[oldc - min] = oldp;
|
||||
} else if (min < c) {
|
||||
// The new character is above the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = c - min + 1;
|
||||
next.table = (generic_mtrie_t **) realloc (
|
||||
next.table, sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = old_count; i != count; i++)
|
||||
next.table[i] = NULL;
|
||||
} else {
|
||||
// The new character is below the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = (min + old_count) - c;
|
||||
next.table = (generic_mtrie_t **) realloc (
|
||||
next.table, sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table + min - c, next.table,
|
||||
old_count * sizeof (generic_mtrie_t *));
|
||||
for (unsigned short i = 0; i != min - c; i++)
|
||||
next.table[i] = NULL;
|
||||
min = c;
|
||||
}
|
||||
}
|
||||
|
||||
// If next node does not exist, create one.
|
||||
if (count == 1) {
|
||||
if (!next.node) {
|
||||
next.node = new (std::nothrow) generic_mtrie_t;
|
||||
alloc_assert (next.node);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.node->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
} else {
|
||||
if (!next.table[c - min]) {
|
||||
next.table[c - min] = new (std::nothrow) generic_mtrie_t;
|
||||
alloc_assert (next.table[c - min]);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.table[c - min]->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void zmq::generic_mtrie_t<T>::rm (value_t *pipe_,
|
||||
void (*func_) (prefix_t data_,
|
||||
size_t size_,
|
||||
void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
unsigned char *buff = NULL;
|
||||
rm_helper (pipe_, &buff, 0, 0, func_, arg_, call_on_uniq_);
|
||||
free (buff);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void zmq::generic_mtrie_t<T>::rm_helper (value_t *pipe_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (prefix_t data_,
|
||||
size_t size_,
|
||||
void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
// Remove the subscription from this node.
|
||||
if (pipes && pipes->erase (pipe_)) {
|
||||
if (!call_on_uniq_ || pipes->empty ()) {
|
||||
func_ (*buff_, buffsize_, arg_);
|
||||
}
|
||||
|
||||
if (pipes->empty ()) {
|
||||
LIBZMQ_DELETE (pipes);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the buffer.
|
||||
if (buffsize_ >= maxbuffsize_) {
|
||||
maxbuffsize_ = buffsize_ + 256;
|
||||
*buff_ = (unsigned char *) realloc (*buff_, maxbuffsize_);
|
||||
alloc_assert (*buff_);
|
||||
}
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (count == 1) {
|
||||
(*buff_)[buffsize_] = min;
|
||||
buffsize_++;
|
||||
next.node->rm_helper (pipe_, buff_, buffsize_, maxbuffsize_, func_,
|
||||
arg_, call_on_uniq_);
|
||||
|
||||
// Prune the node if it was made redundant by the removal
|
||||
if (next.node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.node);
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
//
|
||||
// New min non-null character in the node table after the removal
|
||||
unsigned char new_min = min + count - 1;
|
||||
// New max non-null character in the node table after the removal
|
||||
unsigned char new_max = min;
|
||||
for (unsigned short c = 0; c != count; c++) {
|
||||
(*buff_)[buffsize_] = min + c;
|
||||
if (next.table[c]) {
|
||||
next.table[c]->rm_helper (pipe_, buff_, buffsize_ + 1, maxbuffsize_,
|
||||
func_, arg_, call_on_uniq_);
|
||||
|
||||
// Prune redundant nodes from the mtrie
|
||||
if (next.table[c]->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.table[c]);
|
||||
|
||||
zmq_assert (live_nodes > 0);
|
||||
--live_nodes;
|
||||
} else {
|
||||
// The node is not redundant, so it's a candidate for being
|
||||
// the new min/max node.
|
||||
//
|
||||
// We loop through the node array from left to right, so the
|
||||
// first non-null, non-redundant node encountered is the new
|
||||
// minimum index. Conversely, the last non-redundant, non-null
|
||||
// node encountered is the new maximum index.
|
||||
if (c + min < new_min)
|
||||
new_min = c + min;
|
||||
if (c + min > new_max)
|
||||
new_max = c + min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zmq_assert (count > 1);
|
||||
|
||||
// Free the node table if it's no longer used.
|
||||
if (live_nodes == 0) {
|
||||
free (next.table);
|
||||
next.table = NULL;
|
||||
count = 0;
|
||||
}
|
||||
// Compact the node table if possible
|
||||
else if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
zmq_assert (new_min == new_max);
|
||||
zmq_assert (new_min >= min && new_min < min + count);
|
||||
generic_mtrie_t *node = next.table[new_min - min];
|
||||
zmq_assert (node);
|
||||
free (next.table);
|
||||
next.node = node;
|
||||
count = 1;
|
||||
min = new_min;
|
||||
} else if (new_min > min || new_max < min + count - 1) {
|
||||
zmq_assert (new_max - new_min + 1 > 1);
|
||||
|
||||
generic_mtrie_t **old_table = next.table;
|
||||
zmq_assert (new_min > min || new_max < min + count - 1);
|
||||
zmq_assert (new_min >= min);
|
||||
zmq_assert (new_max <= min + count - 1);
|
||||
zmq_assert (new_max - new_min + 1 < count);
|
||||
|
||||
count = new_max - new_min + 1;
|
||||
next.table =
|
||||
(generic_mtrie_t **) malloc (sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
|
||||
memmove (next.table, old_table + (new_min - min),
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
free (old_table);
|
||||
|
||||
min = new_min;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool zmq::generic_mtrie_t<T>::rm (prefix_t prefix_,
|
||||
size_t size_,
|
||||
value_t *pipe_)
|
||||
{
|
||||
return rm_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool zmq::generic_mtrie_t<T>::rm_helper (prefix_t prefix_,
|
||||
size_t size_,
|
||||
value_t *pipe_)
|
||||
{
|
||||
if (!size_) {
|
||||
if (pipes) {
|
||||
typename pipes_t::size_type erased = pipes->erase (pipe_);
|
||||
zmq_assert (erased == 1);
|
||||
if (pipes->empty ()) {
|
||||
LIBZMQ_DELETE (pipes);
|
||||
}
|
||||
}
|
||||
return !pipes;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (!count || c < min || c >= min + count)
|
||||
return false;
|
||||
|
||||
generic_mtrie_t *next_node = count == 1 ? next.node : next.table[c - min];
|
||||
|
||||
if (!next_node)
|
||||
return false;
|
||||
|
||||
bool ret = next_node->rm_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
|
||||
if (next_node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next_node);
|
||||
zmq_assert (count > 0);
|
||||
|
||||
if (count == 1) {
|
||||
next.node = 0;
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
} else {
|
||||
next.table[c - min] = 0;
|
||||
zmq_assert (live_nodes > 1);
|
||||
--live_nodes;
|
||||
|
||||
// Compact the table if possible
|
||||
if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
unsigned short i;
|
||||
for (i = 0; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count = 1;
|
||||
generic_mtrie_t *oldp = next.table[i];
|
||||
free (next.table);
|
||||
next.node = oldp;
|
||||
} else if (c == min) {
|
||||
// We can compact the table "from the left"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count -= i;
|
||||
generic_mtrie_t **old_table = next.table;
|
||||
next.table = (generic_mtrie_t **) malloc (
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table + i,
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
free (old_table);
|
||||
} else if (c == min + count - 1) {
|
||||
// We can compact the table "from the right"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[count - 1 - i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
count -= i;
|
||||
generic_mtrie_t **old_table = next.table;
|
||||
next.table = (generic_mtrie_t **) malloc (
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table,
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
free (old_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void zmq::generic_mtrie_t<T>::match (prefix_t data_,
|
||||
size_t size_,
|
||||
void (*func_) (value_t *pipe_, void *arg_),
|
||||
void *arg_)
|
||||
{
|
||||
generic_mtrie_t *current = this;
|
||||
while (true) {
|
||||
// Signal the pipes attached to this node.
|
||||
if (current->pipes) {
|
||||
for (typename pipes_t::iterator it = current->pipes->begin ();
|
||||
it != current->pipes->end (); ++it)
|
||||
func_ (*it, arg_);
|
||||
}
|
||||
|
||||
// If we are at the end of the message, there's nothing more to match.
|
||||
if (!size_)
|
||||
break;
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (current->count == 0)
|
||||
break;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (current->count == 1) {
|
||||
if (data_[0] != current->min)
|
||||
break;
|
||||
current = current->next.node;
|
||||
data_++;
|
||||
size_--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
if (data_[0] < current->min
|
||||
|| data_[0] >= current->min + current->count)
|
||||
break;
|
||||
if (!current->next.table[data_[0] - current->min])
|
||||
break;
|
||||
current = current->next.table[data_[0] - current->min];
|
||||
data_++;
|
||||
size_--;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> bool zmq::generic_mtrie_t<T>::is_redundant () const
|
||||
{
|
||||
return !pipes && live_nodes == 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
385
src/mtrie.cpp
385
src/mtrie.cpp
@ -28,389 +28,10 @@
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <new>
|
||||
#include <algorithm>
|
||||
|
||||
#include "err.hpp"
|
||||
#include "pipe.hpp"
|
||||
#include "macros.hpp"
|
||||
#include "mtrie.hpp"
|
||||
#include "generic_mtrie_impl.hpp"
|
||||
|
||||
zmq::mtrie_t::mtrie_t () : pipes (0), min (0), count (0), live_nodes (0)
|
||||
namespace zmq
|
||||
{
|
||||
}
|
||||
|
||||
zmq::mtrie_t::~mtrie_t ()
|
||||
{
|
||||
LIBZMQ_DELETE (pipes);
|
||||
|
||||
if (count == 1) {
|
||||
zmq_assert (next.node);
|
||||
LIBZMQ_DELETE (next.node);
|
||||
} else if (count > 1) {
|
||||
for (unsigned short i = 0; i != count; ++i) {
|
||||
LIBZMQ_DELETE (next.table[i]);
|
||||
}
|
||||
free (next.table);
|
||||
}
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::add (prefix_t prefix_, size_t size_, pipe_t *pipe_)
|
||||
{
|
||||
return add_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::add_helper (prefix_t prefix_, size_t size_, pipe_t *pipe_)
|
||||
{
|
||||
// We are at the node corresponding to the prefix. We are done.
|
||||
if (!size_) {
|
||||
bool result = !pipes;
|
||||
if (!pipes) {
|
||||
pipes = new (std::nothrow) pipes_t;
|
||||
alloc_assert (pipes);
|
||||
}
|
||||
pipes->insert (pipe_);
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (c < min || c >= min + count) {
|
||||
// The character is out of range of currently handled
|
||||
// characters. We have to extend the table.
|
||||
if (!count) {
|
||||
min = c;
|
||||
count = 1;
|
||||
next.node = NULL;
|
||||
} else if (count == 1) {
|
||||
unsigned char oldc = min;
|
||||
mtrie_t *oldp = next.node;
|
||||
count = (min < c ? c - min : min - c) + 1;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = 0; i != count; ++i)
|
||||
next.table[i] = 0;
|
||||
min = std::min (min, c);
|
||||
next.table[oldc - min] = oldp;
|
||||
} else if (min < c) {
|
||||
// The new character is above the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = c - min + 1;
|
||||
next.table =
|
||||
(mtrie_t **) realloc (next.table, sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = old_count; i != count; i++)
|
||||
next.table[i] = NULL;
|
||||
} else {
|
||||
// The new character is below the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = (min + old_count) - c;
|
||||
next.table =
|
||||
(mtrie_t **) realloc (next.table, sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table + min - c, next.table,
|
||||
old_count * sizeof (mtrie_t *));
|
||||
for (unsigned short i = 0; i != min - c; i++)
|
||||
next.table[i] = NULL;
|
||||
min = c;
|
||||
}
|
||||
}
|
||||
|
||||
// If next node does not exist, create one.
|
||||
if (count == 1) {
|
||||
if (!next.node) {
|
||||
next.node = new (std::nothrow) mtrie_t;
|
||||
alloc_assert (next.node);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.node->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
} else {
|
||||
if (!next.table[c - min]) {
|
||||
next.table[c - min] = new (std::nothrow) mtrie_t;
|
||||
alloc_assert (next.table[c - min]);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.table[c - min]->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void zmq::mtrie_t::rm (pipe_t *pipe_,
|
||||
void (*func_) (prefix_t data_, size_t size_, void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
unsigned char *buff = NULL;
|
||||
rm_helper (pipe_, &buff, 0, 0, func_, arg_, call_on_uniq_);
|
||||
free (buff);
|
||||
}
|
||||
|
||||
void zmq::mtrie_t::rm_helper (pipe_t *pipe_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (prefix_t data_,
|
||||
size_t size_,
|
||||
void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
// Remove the subscription from this node.
|
||||
if (pipes && pipes->erase (pipe_)) {
|
||||
if (!call_on_uniq_ || pipes->empty ()) {
|
||||
func_ (*buff_, buffsize_, arg_);
|
||||
}
|
||||
|
||||
if (pipes->empty ()) {
|
||||
LIBZMQ_DELETE (pipes);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the buffer.
|
||||
if (buffsize_ >= maxbuffsize_) {
|
||||
maxbuffsize_ = buffsize_ + 256;
|
||||
*buff_ = (unsigned char *) realloc (*buff_, maxbuffsize_);
|
||||
alloc_assert (*buff_);
|
||||
}
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (count == 1) {
|
||||
(*buff_)[buffsize_] = min;
|
||||
buffsize_++;
|
||||
next.node->rm_helper (pipe_, buff_, buffsize_, maxbuffsize_, func_,
|
||||
arg_, call_on_uniq_);
|
||||
|
||||
// Prune the node if it was made redundant by the removal
|
||||
if (next.node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.node);
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
//
|
||||
// New min non-null character in the node table after the removal
|
||||
unsigned char new_min = min + count - 1;
|
||||
// New max non-null character in the node table after the removal
|
||||
unsigned char new_max = min;
|
||||
for (unsigned short c = 0; c != count; c++) {
|
||||
(*buff_)[buffsize_] = min + c;
|
||||
if (next.table[c]) {
|
||||
next.table[c]->rm_helper (pipe_, buff_, buffsize_ + 1, maxbuffsize_,
|
||||
func_, arg_, call_on_uniq_);
|
||||
|
||||
// Prune redundant nodes from the mtrie
|
||||
if (next.table[c]->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.table[c]);
|
||||
|
||||
zmq_assert (live_nodes > 0);
|
||||
--live_nodes;
|
||||
} else {
|
||||
// The node is not redundant, so it's a candidate for being
|
||||
// the new min/max node.
|
||||
//
|
||||
// We loop through the node array from left to right, so the
|
||||
// first non-null, non-redundant node encountered is the new
|
||||
// minimum index. Conversely, the last non-redundant, non-null
|
||||
// node encountered is the new maximum index.
|
||||
if (c + min < new_min)
|
||||
new_min = c + min;
|
||||
if (c + min > new_max)
|
||||
new_max = c + min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zmq_assert (count > 1);
|
||||
|
||||
// Free the node table if it's no longer used.
|
||||
if (live_nodes == 0) {
|
||||
free (next.table);
|
||||
next.table = NULL;
|
||||
count = 0;
|
||||
}
|
||||
// Compact the node table if possible
|
||||
else if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
zmq_assert (new_min == new_max);
|
||||
zmq_assert (new_min >= min && new_min < min + count);
|
||||
mtrie_t *node = next.table[new_min - min];
|
||||
zmq_assert (node);
|
||||
free (next.table);
|
||||
next.node = node;
|
||||
count = 1;
|
||||
min = new_min;
|
||||
} else if (new_min > min || new_max < min + count - 1) {
|
||||
zmq_assert (new_max - new_min + 1 > 1);
|
||||
|
||||
mtrie_t **old_table = next.table;
|
||||
zmq_assert (new_min > min || new_max < min + count - 1);
|
||||
zmq_assert (new_min >= min);
|
||||
zmq_assert (new_max <= min + count - 1);
|
||||
zmq_assert (new_max - new_min + 1 < count);
|
||||
|
||||
count = new_max - new_min + 1;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
|
||||
memmove (next.table, old_table + (new_min - min),
|
||||
sizeof (mtrie_t *) * count);
|
||||
free (old_table);
|
||||
|
||||
min = new_min;
|
||||
}
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::rm (prefix_t prefix_, size_t size_, pipe_t *pipe_)
|
||||
{
|
||||
return rm_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::rm_helper (prefix_t prefix_, size_t size_, pipe_t *pipe_)
|
||||
{
|
||||
if (!size_) {
|
||||
if (pipes) {
|
||||
pipes_t::size_type erased = pipes->erase (pipe_);
|
||||
zmq_assert (erased == 1);
|
||||
if (pipes->empty ()) {
|
||||
LIBZMQ_DELETE (pipes);
|
||||
}
|
||||
}
|
||||
return !pipes;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (!count || c < min || c >= min + count)
|
||||
return false;
|
||||
|
||||
mtrie_t *next_node = count == 1 ? next.node : next.table[c - min];
|
||||
|
||||
if (!next_node)
|
||||
return false;
|
||||
|
||||
bool ret = next_node->rm_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
|
||||
if (next_node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next_node);
|
||||
zmq_assert (count > 0);
|
||||
|
||||
if (count == 1) {
|
||||
next.node = 0;
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
} else {
|
||||
next.table[c - min] = 0;
|
||||
zmq_assert (live_nodes > 1);
|
||||
--live_nodes;
|
||||
|
||||
// Compact the table if possible
|
||||
if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
unsigned short i;
|
||||
for (i = 0; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count = 1;
|
||||
mtrie_t *oldp = next.table[i];
|
||||
free (next.table);
|
||||
next.node = oldp;
|
||||
} else if (c == min) {
|
||||
// We can compact the table "from the left"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count -= i;
|
||||
mtrie_t **old_table = next.table;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table + i, sizeof (mtrie_t *) * count);
|
||||
free (old_table);
|
||||
} else if (c == min + count - 1) {
|
||||
// We can compact the table "from the right"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[count - 1 - i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
count -= i;
|
||||
mtrie_t **old_table = next.table;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table, sizeof (mtrie_t *) * count);
|
||||
free (old_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void zmq::mtrie_t::match (prefix_t data_,
|
||||
size_t size_,
|
||||
void (*func_) (pipe_t *pipe_, void *arg_),
|
||||
void *arg_)
|
||||
{
|
||||
mtrie_t *current = this;
|
||||
while (true) {
|
||||
// Signal the pipes attached to this node.
|
||||
if (current->pipes) {
|
||||
for (pipes_t::iterator it = current->pipes->begin ();
|
||||
it != current->pipes->end (); ++it)
|
||||
func_ (*it, arg_);
|
||||
}
|
||||
|
||||
// If we are at the end of the message, there's nothing more to match.
|
||||
if (!size_)
|
||||
break;
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (current->count == 0)
|
||||
break;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (current->count == 1) {
|
||||
if (data_[0] != current->min)
|
||||
break;
|
||||
current = current->next.node;
|
||||
data_++;
|
||||
size_--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
if (data_[0] < current->min
|
||||
|| data_[0] >= current->min + current->count)
|
||||
break;
|
||||
if (!current->next.table[data_[0] - current->min])
|
||||
break;
|
||||
current = current->next.table[data_[0] - current->min];
|
||||
data_++;
|
||||
size_--;
|
||||
}
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::is_redundant () const
|
||||
{
|
||||
return !pipes && live_nodes == 0;
|
||||
template class generic_mtrie_t<pipe_t>;
|
||||
}
|
||||
|
@ -30,76 +30,23 @@
|
||||
#ifndef __ZMQ_MTRIE_HPP_INCLUDED__
|
||||
#define __ZMQ_MTRIE_HPP_INCLUDED__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <set>
|
||||
#include "generic_mtrie.hpp"
|
||||
|
||||
#include "stdint.hpp"
|
||||
#if __cplusplus >= 201103L || defined(_MSC_VER)
|
||||
#define ZMQ_HAS_EXTERN_TEMPLATE 1
|
||||
#else
|
||||
#define ZMQ_HAS_EXTERN_TEMPLATE 0
|
||||
#endif
|
||||
|
||||
namespace zmq
|
||||
{
|
||||
class pipe_t;
|
||||
|
||||
// Multi-trie. Each node in the trie is a set of pointers to pipes.
|
||||
#if ZMQ_HAS_EXTERN_TEMPLATE
|
||||
extern template class generic_mtrie_t<pipe_t>;
|
||||
#endif
|
||||
|
||||
class mtrie_t
|
||||
{
|
||||
public:
|
||||
typedef const unsigned char *prefix_t;
|
||||
|
||||
mtrie_t ();
|
||||
~mtrie_t ();
|
||||
|
||||
// Add key to the trie. Returns true if it's a new subscription
|
||||
// rather than a duplicate.
|
||||
bool add (prefix_t prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
|
||||
// Remove all subscriptions for a specific peer from the trie.
|
||||
// The call_on_uniq_ flag controls if the callback is invoked
|
||||
// when there are no subscriptions left on some topics or on
|
||||
// every removal.
|
||||
void
|
||||
rm (zmq::pipe_t *pipe_,
|
||||
void (*func_) (const unsigned char *data_, size_t size_, void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_);
|
||||
|
||||
// Remove specific subscription from the trie. Return true is it was
|
||||
// actually removed rather than de-duplicated.
|
||||
bool rm (prefix_t prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
|
||||
// Signal all the matching pipes.
|
||||
void match (prefix_t data_,
|
||||
size_t size_,
|
||||
void (*func_) (zmq::pipe_t *pipe_, void *arg_),
|
||||
void *arg_);
|
||||
|
||||
private:
|
||||
bool add_helper (prefix_t prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
void rm_helper (zmq::pipe_t *pipe_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (prefix_t data_, size_t size_, void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_);
|
||||
bool rm_helper (prefix_t prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
bool is_redundant () const;
|
||||
|
||||
typedef std::set<zmq::pipe_t *> pipes_t;
|
||||
pipes_t *pipes;
|
||||
|
||||
unsigned char min;
|
||||
unsigned short count;
|
||||
unsigned short live_nodes;
|
||||
union
|
||||
{
|
||||
class mtrie_t *node;
|
||||
class mtrie_t **table;
|
||||
} next;
|
||||
|
||||
mtrie_t (const mtrie_t &);
|
||||
const mtrie_t &operator= (const mtrie_t &);
|
||||
};
|
||||
typedef generic_mtrie_t<pipe_t> mtrie_t;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -4,6 +4,7 @@ cmake_minimum_required(VERSION "2.8.1")
|
||||
set(unittests
|
||||
unittest_ypipe
|
||||
unittest_poller
|
||||
unittest_mtrie
|
||||
)
|
||||
|
||||
#IF (ENABLE_DRAFTS)
|
||||
|
113
unittests/unittest_mtrie.cpp
Normal file
113
unittests/unittest_mtrie.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright (c) 2018 Contributors as noted in the AUTHORS file
|
||||
|
||||
This file is part of 0MQ.
|
||||
|
||||
0MQ is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
0MQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../tests/testutil.hpp"
|
||||
|
||||
#if defined(min)
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#include <generic_mtrie_impl.hpp>
|
||||
|
||||
#include <unity.h>
|
||||
|
||||
void setUp ()
|
||||
{
|
||||
}
|
||||
void tearDown ()
|
||||
{
|
||||
}
|
||||
|
||||
void test_create ()
|
||||
{
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
}
|
||||
|
||||
void mtrie_count (int *pipe, void *arg)
|
||||
{
|
||||
LIBZMQ_UNUSED (pipe);
|
||||
int *count = static_cast<int *> (arg);
|
||||
++*count;
|
||||
}
|
||||
|
||||
void test_check_empty_match_nonempty_data ()
|
||||
{
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, 3, mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
|
||||
void test_check_empty_match_empty_data ()
|
||||
{
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (NULL, 0, mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
|
||||
void test_add_single_entry_match_exact ()
|
||||
{
|
||||
int pipe;
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
bool res = mtrie.add (test_name, 3, &pipe);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, 3, mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (1, count);
|
||||
}
|
||||
|
||||
void test_add_rm_single_entry_match_exact ()
|
||||
{
|
||||
int pipe;
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
mtrie.add (test_name, 3, &pipe);
|
||||
bool res = mtrie.rm (test_name, 3, &pipe);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, 3, mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
|
||||
int main (void)
|
||||
{
|
||||
setup_test_environment ();
|
||||
|
||||
UNITY_BEGIN ();
|
||||
RUN_TEST (test_create);
|
||||
RUN_TEST (test_check_empty_match_nonempty_data);
|
||||
RUN_TEST (test_check_empty_match_empty_data);
|
||||
RUN_TEST (test_add_single_entry_match_exact);
|
||||
RUN_TEST (test_add_rm_single_entry_match_exact);
|
||||
|
||||
return UNITY_END ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user