diff options
Diffstat (limited to 'src')
111 files changed, 12680 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..bb648ec --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,120 @@ +lib_LTLIBRARIES = libzs.la + +libzs_la_SOURCES = \ + app_thread.hpp \ + atomic_bitmap.hpp \ + atomic_counter.hpp \ + atomic_ptr.hpp \ + command.hpp \ + config.hpp \ + connecter.hpp \ + data_distributor.hpp \ + decoder.hpp \ + devpoll.hpp \ + dispatcher.hpp \ + dummy_aggregator.hpp \ + dummy_distributor.hpp \ + encoder.hpp \ + epoll.hpp \ + err.hpp \ + fair_aggregator.hpp \ + fd.hpp \ + fd_signaler.hpp \ + io_object.hpp \ + io_thread.hpp \ + ip.hpp \ + i_api.hpp \ + i_demux.hpp \ + i_mux.hpp \ + i_poller.hpp \ + i_poll_events.hpp \ + i_session.hpp \ + i_signaler.hpp \ + i_engine.hpp \ + i_thread.hpp \ + listener.hpp \ + kqueue.hpp \ + load_balancer.hpp \ + msg.hpp \ + mutex.hpp \ + object.hpp \ + p2p.hpp \ + pipe.hpp \ + pipe_reader.hpp \ + pipe_writer.hpp \ + platform.hpp \ + poll.hpp \ + pub.hpp \ + rep.hpp \ + req.hpp \ + safe_object.hpp \ + select.hpp \ + session.hpp \ + session_stub.hpp \ + simple_semaphore.hpp \ + socket_base.hpp \ + sub.hpp \ + stdint.hpp \ + tcp_connecter.hpp \ + tcp_listener.hpp \ + tcp_socket.hpp \ + thread.hpp \ + uuid.hpp \ + windows.hpp \ + wire.hpp \ + ypipe.hpp \ + ypollset.hpp \ + yqueue.hpp \ + zmq_decoder.hpp \ + zmq_encoder.hpp \ + zmq_tcp_engine.hpp \ + app_thread.cpp \ + connecter.cpp \ + data_distributor.cpp \ + devpoll.hpp \ + dispatcher.cpp \ + dummy_aggregator.cpp \ + dummy_distributor.cpp \ + epoll.cpp \ + err.cpp \ + fair_aggregator.cpp \ + fd_signaler.cpp \ + io_object.cpp \ + io_thread.cpp \ + ip.cpp \ + kqueue.cpp \ + listener.cpp \ + load_balancer.cpp \ + object.cpp \ + p2p.cpp \ + pipe.cpp \ + pipe_reader.cpp \ + pipe_writer.cpp \ + poll.cpp \ + pub.cpp \ + rep.cpp \ + req.cpp \ + safe_object.cpp \ + select.cpp \ + session.cpp \ + session_stub.cpp \ + socket_base.cpp \ + sub.cpp \ + tcp_connecter.cpp \ + tcp_listener.cpp \ + tcp_socket.cpp \ + thread.cpp \ + uuid.cpp \ + ypollset.cpp \ + zmq_decoder.cpp \ + zmq_encoder.cpp \ + zmq_tcp_engine.cpp \ + zs.cpp + +libzs_la_LDFLAGS = -version-info 0:0:0 +libzs_la_CXXFLAGS = -Wall -pedantic -Werror @ZS_EXTRA_CXXFLAGS@ + +dist-hook: + -rm $(distdir)/src/platform.hpp + + diff --git a/src/app_thread.cpp b/src/app_thread.cpp new file mode 100644 index 0000000..ca08976 --- /dev/null +++ b/src/app_thread.cpp @@ -0,0 +1,221 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#if defined ZS_HAVE_WINDOWS +#include "windows.hpp" +#else +#include <unistd.h> +#endif + +#include "app_thread.hpp" +#include "dispatcher.hpp" +#include "err.hpp" +#include "session.hpp" +#include "pipe.hpp" +#include "config.hpp" +#include "i_api.hpp" +#include "dummy_aggregator.hpp" +#include "fair_aggregator.hpp" +#include "dummy_distributor.hpp" +#include "data_distributor.hpp" +#include "load_balancer.hpp" +#include "p2p.hpp" +#include "pub.hpp" +#include "sub.hpp" +#include "req.hpp" +#include "rep.hpp" + +// If the RDTSC is available we use it to prevent excessive +// polling for commands. The nice thing here is that it will work on any +// system with x86 architecture and gcc or MSVC compiler. +#if (defined __GNUC__ && (defined __i386__ || defined __x86_64__)) ||\ + (defined _MSC_VER && (defined _M_IX86 || defined _M_X64)) +#define ZS_DELAY_COMMANDS +#endif + +zs::app_thread_t::app_thread_t (dispatcher_t *dispatcher_, int thread_slot_) : + object_t (dispatcher_, thread_slot_), + tid (0), + last_processing_time (0) +{ +} + +void zs::app_thread_t::shutdown () +{ + // Deallocate all the sessions associated with the thread. + while (!sessions.empty ()) + sessions [0]->shutdown (); + + delete this; +} + +zs::app_thread_t::~app_thread_t () +{ +} + +void zs::app_thread_t::attach_session (session_t *session_) +{ + session_->set_index (sessions.size ()); + sessions.push_back (session_); +} + +void zs::app_thread_t::detach_session (session_t *session_) +{ + // O(1) removal of the session from the list. + sessions_t::size_type i = session_->get_index (); + sessions [i] = sessions [sessions.size () - 1]; + sessions [i]->set_index (i); + sessions.pop_back (); +} + +zs::i_poller *zs::app_thread_t::get_poller () +{ + zs_assert (false); +} + +zs::i_signaler *zs::app_thread_t::get_signaler () +{ + return &pollset; +} + +bool zs::app_thread_t::is_current () +{ + return !sessions.empty () && tid == getpid (); +} + +bool zs::app_thread_t::make_current () +{ + // If there are object managed by this slot we cannot assign the slot + // to a different thread. + if (!sessions.empty ()) + return false; + + tid = getpid (); + return true; +} + +zs::i_api *zs::app_thread_t::create_socket (int type_) +{ + i_mux *mux = NULL; + i_demux *demux = NULL; + session_t *session = NULL; + i_api *api = NULL; + + switch (type_) { + case ZS_P2P: + mux = new dummy_aggregator_t; + zs_assert (mux); + demux = new dummy_distributor_t; + zs_assert (demux); + session = new session_t (this, this, mux, demux, true, false); + zs_assert (session); + api = new p2p_t (this, session); + zs_assert (api); + break; + case ZS_PUB: + demux = new data_distributor_t; + zs_assert (demux); + session = new session_t (this, this, mux, demux, true, false); + zs_assert (session); + api = new pub_t (this, session); + zs_assert (api); + break; + case ZS_SUB: + mux = new fair_aggregator_t; + zs_assert (mux); + session = new session_t (this, this, mux, demux, true, false); + zs_assert (session); + api = new sub_t (this, session); + zs_assert (api); + break; + case ZS_REQ: + // TODO + zs_assert (false); + api = new req_t (this, session); + zs_assert (api); + break; + case ZS_REP: + // TODO + zs_assert (false); + api = new rep_t (this, session); + zs_assert (api); + break; + default: + errno = EINVAL; + return NULL; + } + + attach_session (session); + + return api; +} + +void zs::app_thread_t::process_commands (bool block_) +{ + ypollset_t::signals_t signals; + if (block_) + signals = pollset.poll (); + else { + +#if defined ZS_DELAY_COMMANDS + // Optimised version of command processing - it doesn't have to check + // for incoming commands each time. It does so only if certain time + // elapsed since last command processing. Command delay varies + // depending on CPU speed: It's ~1ms on 3GHz CPU, ~2ms on 1.5GHz CPU + // etc. The optimisation makes sense only on platforms where getting + // a timestamp is a very cheap operation (tens of nanoseconds). + + // Get timestamp counter. +#if defined __GNUC__ + uint32_t low; + uint32_t high; + __asm__ volatile ("rdtsc" : "=a" (low), "=d" (high)); + uint64_t current_time = (uint64_t) high << 32 | low; +#elif defined _MSC_VER + uint64_t current_time = __rdtsc (); +#else +#error +#endif + + // Check whether certain time have elapsed since last command + // processing. + if (current_time - last_processing_time <= max_command_delay) + return; + last_processing_time = current_time; +#endif + + // Check whether there are any commands pending for this thread. + signals = pollset.check (); + } + + if (signals) { + + // Traverse all the possible sources of commands and process + // all the commands from all of them. + for (int i = 0; i != thread_slot_count (); i++) { + if (signals & (ypollset_t::signals_t (1) << i)) { + command_t cmd; + while (dispatcher->read (i, get_thread_slot (), &cmd)) + cmd.destination->process_command (cmd); + } + } + } +} diff --git a/src/app_thread.hpp b/src/app_thread.hpp new file mode 100644 index 0000000..61e7ff1 --- /dev/null +++ b/src/app_thread.hpp @@ -0,0 +1,95 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_APP_THREAD_HPP_INCLUDED__ +#define __ZS_APP_THREAD_HPP_INCLUDED__ + +#include <vector> + +#include "i_thread.hpp" +#include "stdint.hpp" +#include "object.hpp" +#include "ypollset.hpp" + +namespace zs +{ + + class app_thread_t : public object_t, public i_thread + { + public: + + app_thread_t (class dispatcher_t *dispatcher_, int thread_slot_); + + // To be called when the whole infrastrucure is being closed (zs_term). + void shutdown (); + + // Returns signaler associated with this application thread. + i_signaler *get_signaler (); + + // Create socket engine in this thread. Return false if the calling + // thread doesn't match the thread handled by this app thread object. + struct i_api *create_socket (int type_); + + // Nota bene: The following two functions are accessed from different + // threads. The caller (dispatcher) is responsible for synchronisation + // of accesses. + + // Returns true is current thread is associated with the app thread. + bool is_current (); + + // Tries to associate current thread with the app thread object. + // Returns true is successfull, false otherwise. + bool make_current (); + + // Processes commands sent to this thread (if any). If 'block' is + // set to true, returns only after at least one command was processed. + void process_commands (bool block_); + + // i_thread implementation. + void attach_session (class session_t *session_); + void detach_session (class session_t *session_); + struct i_poller *get_poller (); + + private: + + // Clean-up. + ~app_thread_t (); + + // Thread ID associated with this slot. + // TODO: Virtualise pid_t! + // TODO: Check whether getpid returns unique ID for each thread. + int tid; + + // Vector of all sessionss associated with this app thread. + typedef std::vector <class session_t*> sessions_t; + sessions_t sessions; + + // App thread's signaler object. + ypollset_t pollset; + + // Timestamp of when commands were processed the last time. + uint64_t last_processing_time; + + app_thread_t (const app_thread_t&); + void operator = (const app_thread_t&); + }; + +} + +#endif diff --git a/src/atomic.hpp b/src/atomic.hpp new file mode 100644 index 0000000..e24b719 --- /dev/null +++ b/src/atomic.hpp @@ -0,0 +1,310 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_ATOMIC_HPP_INCLUDED__ +#define __ZS_ATOMIC_HPP_INCLUDED__ + +#include "stdint.hpp" + +#if defined ZS_FORCE_MUTEXES +#define ZS_ATOMIC_MUTEX +#elif (defined __i386__ || defined __x86_64__) && defined __GNUC__ +#define ZS_ATOMIC_X86 +#elif defined ZMQ_HAVE_WINDOWS +#define ZS_ATOMIC_WINDOWS +#elif defined ZMQ_HAVE_SOLARIS +#define ZS_ATOMIC_SOLARIS +#else +#define ZS_ATOMIC_MUTEX +#endif + +namespace zs +{ + + // Atomic assignement. + inline void atomic_uint32_set (volatile uint32_t *p_, uint32_t value_) + { + *p_ = value_; + // StoreLoad memory barrier should go here on platforms with + // memory models that require it. + } + + // Atomic retrieval of an integer. + inline uint32_t atomic_uint32_get (volatile uint32_t *p_) + { + // StoreLoad memory barrier should go here on platforms with + // memory models that require it. + return *p_; + } + + // Atomic addition. Returns the old value. + inline uint32_t atomic_uint32_add (volatile uint32_t *p_, uint32_t delta_) + { +#if defined ZS_ATOMIC_WINDOWS + return InterlockedExchangeAdd ((LONG*) &value, increment_); +#elif defined ZS_ATOMIC_SOLARIS + return atomic_add_32_nv (&value, increment_) - delta_; +#elif defined ZS_ATOMIC_X86 + uint32_t old; + __asm__ volatile ( + "lock; xadd %0, %1\n\t" + : "=r" (old), "=m" (*p_) + : "0" (delta_), "m" (*p_) + : "cc", "memory"); + return old; +#else +#error // TODO: + sync.lock (); + uint32_t old = *p_; + *p_ += delta_; + sync.unlock (); +#endif + } + + // Atomic subtraction. Returns the old value. + inline uint32_t atomic_uint32_sub (volatile uint32_t *p_, uint32_t delta_) + { +#if defined ZS_ATOMIC_WINDOWS + LONG delta = - ((LONG) delta_); + return InterlockedExchangeAdd ((LONG*) &value, delta); +#elif defined ZS_ATOMIC_SOLARIS + int32_t delta = - ((int32_t) delta_); + return atomic_add_32_nv (&value, delta) + delta_; +#elif defined ZS_ATOMIC_X86 + uint32_t old = -delta_; + __asm__ volatile ("lock; xaddl %0,%1" + : "=r" (old), "=m" (*p_) + : "0" (old), "m" (*p_) + : "cc"); + return old; +#else +#error // TODO: + sync.lock (); + uint32_t old = *p_; + *p_ -= delta_; + sync.unlock (); + return old; +#endif + } + + // Atomic assignement. + template <typename T> + inline void atomic_ptr_set (volatile T **p_, T *value_) + { + *p_ = value_; + // StoreLoad memory barrier should go here on platforms with + // memory models that require it. + } + + // Perform atomic 'exchange pointers' operation. Old value is returned. + template <typename T> + inline void *atomic_ptr_xchg (volatile T **p_, T *value_) + { +#if defined ZS_ATOMIC_WINDOWS + return InterlockedExchangePointer (p_, value_); +#elif defined ZS_ATOMIC_SOLARIS + return atomic_swap_ptr (p_, value_); +#elif defined ZS_ATOMIC_X86 + void *old; + __asm__ volatile ( + "lock; xchg %0, %2" + : "=r" (old), "=m" (*p_) + : "m" (*p_), "0" (value_)); + return old; +#else +#error // TODO: + sync.lock (); + void *old = *p_; + *p_ = value_; + sync.unlock (); + return old; +#endif + } + + // Perform atomic 'compare and swap' operation on the pointer. + // The pointer is compared to 'cmp' argument and if they are + // equal, its value is set to 'value'. Old value of the pointer + // is returned. + template <typename T> + inline void *atomic_ptr_cas (volatile T **p_, T *cmp_, T *value_) + { +#if defined ZS_ATOMIC_WINDOWS + return InterlockedCompareExchangePointer (p_, value_, cmp_); +#elif defined ZS_ATOMIC_SOLARIS + return atomic_cas_ptr (p_, cmp_, value_); +#elif defined ZS_ATOMIC_X86 + void *old; + __asm__ volatile ( + "lock; cmpxchg %2, %3" + : "=a" (old), "=m" (*p_) + : "r" (value_), "m" (*p_), "0" (cmp_) + : "cc"); + return old; +#else +#error // TODO: + sync.lock (); + void *old = *p_; + if (old == cmp_) + *p_ = value_; + sync.unlock (); + return old; +#endif + } + +#if defined ZS_ATOMIC_X86 && defined __x86_64__ + typedef uint64_t atomic_bitmap_t; +#else + typedef uint32_t atomic_bitmap_t; +#endif + + // Atomic assignement. + inline void atomic_bitmap_set (volatile atomic_bitmap_t *p_, + atomic_bitmap_t value_) + { + *p_ = value_; + // StoreLoad memory barrier should go here on platforms with + // memory models that require it. + } + + // Bit-test-set-and-reset. Sets one bit of the value and resets + // another one. Returns the original value of the reset bit. + inline bool atomic_bitmap_btsr (volatile atomic_bitmap_t *p_, + int set_index_, int reset_index_) + { +#if defined ZS_ATOMIC_WINDOWS + while (true) { + atomic_bitmap_t oldval = *p_; + atomic_bitmap_t newval = (oldval | (atomic_bitmap_t (1) << + set_index_)) & ~(integer_t (1) << reset_index_); + if (InterlockedCompareExchange ((volatile LONG*) p_, newval, + oldval) == (LONG) oldval) + return (oldval & (atomic_bitmap_t (1) << reset_index_)) ? + true : false; + } +#elif defined ZS_ATOMIC_SOLARIS + while (true) { + atomic_bitmap_t oldval = *p_; + atomic_bitmap_t newval = (oldval | (atomic_bitmap_t (1) << + set_index_)) & ~(integer_t (1) << reset_index_); + if (atomic_cas_32 (p_, oldval, newval) == oldval) + return (oldval & (atomic_bitmap_t (1) << reset_index_)) ? + true : false; + } +#elif defined ZS_ATOMIC_X86 + atomic_bitmap_t oldval, dummy; + __asm__ volatile ( + "mov %0, %1\n\t" + "1:" + "mov %1, %2\n\t" + "bts %3, %2\n\t" + "btr %4, %2\n\t" + "lock cmpxchg %2, %0\n\t" + "jnz 1b\n\t" + : "+m" (*p_), "=&a" (oldval), "=&r" (dummy) + : "r" (atomic_bitmap_t (set_index_)), + "r" (atomic_bitmap_t (reset_index_)) + : "cc"); + return (bool) (oldval & (atomic_bitmap_t (1) << reset_index_)); +#else +#error // TODO: + sync.lock (); + atomic_bitmap_t oldval = *p_; + *p_ = (oldval | (atomic_bitmap_t (1) << set_index_)) & + ~(atomic_bitmap_t (1) << reset_index_); + sync.unlock (); + return (oldval & (atomic_bitmap_t (1) << reset_index_)) ? true : false; +#endif + } + + // Sets value to newval. Returns the original value. + inline atomic_bitmap_t atomic_bitmap_xchg (volatile atomic_bitmap_t *p_, + atomic_bitmap_t newval_) + { +#if defined ZS_ATOMIC_WINDOWS + return InterlockedExchange ((volatile LONG*) p_, newval_); +#elif defined ZS_ATOMIC_SOLARIS + return atomic_swap_32 (p_, newval_); +#elif defined ZS_ATOMIC_X86 + atomic_bitmap_t oldval = newval_; + __asm__ volatile ( + "lock; xchg %0, %1" + : "=r" (oldval) + : "m" (*p_), "0" (oldval) + : "memory"); + return oldval; +#else +#error // TODO: + sync.lock (); + atomic_bitmap_t oldval = *p_; + *p_ = newval_; + sync.unlock (); +#endif + } + + // izte is "if-zero-then-else" atomic operation - if the value is zero + // it substitutes it by 'thenval' else it rewrites it by 'elseval'. + // Original value of the integer is returned from this function. + inline atomic_bitmap_t atomic_bitmap_izte (volatile atomic_bitmap_t *p_, + atomic_bitmap_t thenval_, atomic_bitmap_t elseval_) + { +#if defined ZS_ATOMIC_WINDOWS + while (true) { + atomic_bitmap_t oldval = *p_; + atomic_bitmap_t newval = (oldval ? elseval_ : thenval_); + if (InterlockedCompareExchange ((volatile LONG*) p_, newval, + oldval) == (LONG) oldval) + return oldval; + } +#elif defined ZS_ATOMIC_SOLARIS + while (true) { + atomic_bitmap_t oldval = *p_; + atomic_bitmap_t newval = (oldval ? elseval_ : thenval_); + if (atomic_cas_32 (p_, oldval, newval) == oldval) + return oldval; + } +#elif defined ZS_ATOMIC_X86 + atomic_bitmap_t oldval; + atomic_bitmap_t dummy; + __asm__ volatile ( + "mov %0, %1\n\t" + "1:" + "mov %3, %2\n\t" + "test %1, %1\n\t" + "jz 2f\n\t" + "mov %4, %2\n\t" + "2:" + "lock cmpxchg %2, %0\n\t" + "jnz 1b\n\t" + : "+m" (*p_), "=&a" (oldval), "=&r" (dummy) + : "r" (thenval_), "r" (elseval_) + : "cc"); + return oldval; +#else +#error // TODO: + sync.lock (); + atomic_bitmap_t oldval = *p_; + *p_ = oldval ? elseval_ : thenval_; + sync.unlock (); + return oldval; +#endif + } + +} + +#endif diff --git a/src/atomic_bitmap.hpp b/src/atomic_bitmap.hpp new file mode 100644 index 0000000..a5440de --- /dev/null +++ b/src/atomic_bitmap.hpp @@ -0,0 +1,286 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_ATOMIC_BITMAP_HPP_INCLUDED__ +#define __ZS_ATOMIC_BITMAP_HPP_INCLUDED__ + +#include "stdint.hpp" +#include "platform.hpp" + +// These are the conditions to choose between different implementations +// of atomic_bitmap. + +#if defined ZS_FORCE_MUTEXES +#define ZS_ATOMIC_BITMAP_MUTEX +#elif (defined __i386__ || defined __x86_64__) && defined __GNUC__ +#define ZS_ATOMIC_BITMAP_X86 +#elif 0 && defined __sparc__ && defined __GNUC__ +#define ZS_ATOMIC_BITMAP_SPARC +#elif defined ZS_HAVE_WINDOWS +#define ZS_ATOMIC_BITMAP_WINDOWS +#elif defined ZS_HAVE_SOLARIS +#define ZS_ATOMIC_BITMAP_SOLARIS +#else +#define ZS_ATOMIC_BITMAP_MUTEX +#endif + +#if defined ZS_ATOMIC_BITMAP_MUTEX +#include "mutex.hpp" +#elif defined ZS_ATOMIC_BITMAP_WINDOWS +#include "windows.hpp" +#elif defined ZS_ATOMIC_BITMAP_SOLARIS +#include <atomic.h> +#endif + +namespace zs +{ + + // This class encapuslates several bitwise atomic operations on unsigned + // integer. Selection of operations is driven specifically by the needs + // of ypollset implementation. + + class atomic_bitmap_t + { + public: + +#if (defined ZMQ_ATOMIC_BITMAP_X86 || defined ZMQ_FORCE_MUTEXES) \ + && defined __x86_64__ + typedef uint64_t bitmap_t; +#else + typedef uint32_t bitmap_t; +#endif + + inline atomic_bitmap_t (bitmap_t value_ = 0) : + value (value_) + { + } + + inline ~atomic_bitmap_t () + { + } + + // Bit-test-set-and-reset. Sets one bit of the value and resets + // another one. Returns the original value of the reset bit. + inline bool btsr (int set_index_, int reset_index_) + { +#if defined ZS_ATOMIC_BITMAP_WINDOWS + while (true) { + bitmap_t oldval = value; + bitmap_t newval = (oldval | (bitmap_t (1) << set_index_)) & + ~(bitmap_t (1) << reset_index_); + if (InterlockedCompareExchange ((volatile LONG*) &value, newval, + oldval) == (LONG) oldval) + return (oldval & (bitmap_t (1) << reset_index_)) ? + true : false; + } +#elif defined ZS_ATOMIC_BITMAP_SOLARIS + while (true) { + bitmap_t oldval = value; + bitmap_t newval = (oldval | (bitmap_t (1) << set_index_)) & + ~(bitmap_t (1) << reset_index_); + if (atomic_cas_32 (&value, oldval, newval) == oldval) + return (oldval & (bitmap_t (1) << reset_index_)) ? + true : false; + } +#elif defined ZS_ATOMIC_BITMAP_X86 + bitmap_t oldval, dummy; + __asm__ volatile ( + "mov %0, %1\n\t" + "1:" + "mov %1, %2\n\t" + "bts %3, %2\n\t" + "btr %4, %2\n\t" + "lock cmpxchg %2, %0\n\t" + "jnz 1b\n\t" + : "+m" (value), "=&a" (oldval), "=&r" (dummy) + : "r" (bitmap_t(set_index_)), "r" (bitmap_t(reset_index_)) + : "cc"); + return (bool) (oldval & (bitmap_t(1) << reset_index_)); +#elif defined ZS_ATOMIC_BITMAP_SPARC + volatile bitmap_t* valptr = &value; + bitmap_t set_val = bitmap_t(1) << set_index_; + bitmap_t reset_val = ~(bitmap_t(1) << reset_index_); + bitmap_t tmp; + bitmap_t oldval; + __asm__ volatile( + "ld [%5], %2 \n\t" + "1: \n\t" + "or %2, %0, %3 \n\t" + "and %3, %1, %3 \n\t" + "cas [%5], %2, %3 \n\t" + "cmp %2, %3 \n\t" + "bne,a,pn %%icc, 1b \n\t" + "mov %3, %2 \n\t" + : "+r" (set_val), "+r" (reset_val), "=&r" (tmp), + "=&r" (oldval), "+m" (*valptr) + : "r" (valptr) + : "cc"); + return oldval; +#elif defined ZS_ATOMIC_BITMAP_MUTEX + sync.lock (); + bitmap_t oldval = value; + value = (oldval | (bitmap_t (1) << set_index_)) & + ~(bitmap_t (1) << reset_index_); + sync.unlock (); + return (oldval & (bitmap_t (1) << reset_index_)) ? true : false; +#else +#error +#endif + } + + // Sets value to newval. Returns the original value. + inline bitmap_t xchg (bitmap_t newval_) + { + bitmap_t oldval; +#if defined ZS_ATOMIC_BITMAP_WINDOWS + oldval = InterlockedExchange ((volatile LONG*) &value, newval_); +#elif defined ZS_ATOMIC_BITMAP_SOLARIS + oldval = atomic_swap_32 (&value, newval_); +#elif defined ZS_ATOMIC_BITMAP_X86 + oldval = newval_; + __asm__ volatile ( + "lock; xchg %0, %1" + : "=r" (oldval) + : "m" (value), "0" (oldval) + : "memory"); +#elif defined ZS_ATOMIC_BITMAP_SPARC + oldval = value; + volatile bitmap_t* ptrin = &value; + bitmap_t tmp; + bitmap_t prev; + __asm__ __volatile__( + "ld [%4], %1\n\t" + "1:\n\t" + "mov %0, %2\n\t" + "cas [%4], %1, %2\n\t" + "cmp %1, %2\n\t" + "bne,a,pn %%icc, 1b\n\t" + "mov %2, %1\n\t" + : "+r" (newval_), "=&r" (tmp), "=&r" (prev), "+m" (*ptrin) + : "r" (ptrin) + : "cc"); + return prev; +#elif defined ZS_ATOMIC_BITMAP_MUTEX + sync.lock (); + oldval = value; + value = newval_; + sync.unlock (); +#else +#error +#endif + return oldval; + } + + // izte is "if-zero-then-else" atomic operation - if the value is zero + // it substitutes it by 'thenval' else it rewrites it by 'elseval'. + // Original value of the integer is returned from this function. + inline bitmap_t izte (bitmap_t thenval_, + bitmap_t elseval_) + { +#if defined ZS_ATOMIC_BITMAP_WINDOWS + while (true) { + bitmap_t oldval = value; + bitmap_t newval = oldval == 0 ? thenval_ : elseval_; + if (InterlockedCompareExchange ((volatile LONG*) &value, + newval, oldval) == (LONG) oldval) + return oldval; + } +#elif defined ZS_ATOMIC_BITMAP_SOLARIS + while (true) { + bitmap_t oldval = value; + bitmap_t newval = oldval == 0 ? thenval_ : elseval_; + if (atomic_cas_32 (&value, oldval, newval) == oldval) + return oldval; + } +#elif defined ZS_ATOMIC_BITMAP_X86 + bitmap_t oldval; + bitmap_t dummy; + __asm__ volatile ( + "mov %0, %1\n\t" + "1:" + "mov %3, %2\n\t" + "test %1, %1\n\t" + "jz 2f\n\t" + "mov %4, %2\n\t" + "2:" + "lock cmpxchg %2, %0\n\t" + "jnz 1b\n\t" + : "+m" (value), "=&a" (oldval), "=&r" (dummy) + : "r" (thenval_), "r" (elseval_) + : "cc"); + return oldval; +#elif defined ZS_ATOMIC_BITMAP_SPARC + volatile bitmap_t* ptrin = &value; + bitmap_t tmp; + bitmap_t prev; + __asm__ __volatile__( + "ld [%3], %0 \n\t" + "mov 0, %1 \n\t" + "cas [%3], %1, %4 \n\t" + "cmp %0, %1 \n\t" + "be,a,pn %%icc,1f \n\t" + "ld [%3], %0 \n\t" + "cas [%3], %0, %5 \n\t" + "1: \n\t" + : "=&r" (tmp), "=&r" (prev), "+m" (*ptrin) + : "r" (ptrin), "r" (thenval_), "r" (elseval_) + : "cc"); + return prev; +#elif defined ZS_ATOMIC_BITMAP_MUTEX + sync.lock (); + bitmap_t oldval = value; + value = oldval ? elseval_ : thenval_; + sync.unlock (); + return oldval; +#else +#error +#endif + } + + private: + + volatile bitmap_t value; +#if defined ZS_ATOMIC_BITMAP_MUTEX + mutex_t sync; +#endif + + atomic_bitmap_t (const atomic_bitmap_t&); + void operator = (const atomic_bitmap_t&); + }; + +} + +// Remove macros local to this file. +#if defined ZS_ATOMIC_BITMAP_WINDOWS +#undef ZS_ATOMIC_BITMAP_WINDOWS +#endif +#if defined ZS_ATOMIC_BITMAP_SOLARIS +#undef ZS_ATOMIC_BITMAP_SOLARIS +#endif +#if defined ZS_ATOMIC_BITMAP_X86 +#undef ZS_ATOMIC_BITMAP_X86 +#endif +#if defined ZS_ATOMIC_BITMAP_SPARC +#undef ZS_ATOMIC_BITMAP_SPARC +#endif +#if defined ZS_ATOMIC_BITMAP_MUTEX +#undef ZS_ATOMIC_BITMAP_MUTEX +#endif + +#endif diff --git a/src/atomic_counter.hpp b/src/atomic_counter.hpp new file mode 100644 index 0000000..0873fdd --- /dev/null +++ b/src/atomic_counter.hpp @@ -0,0 +1,197 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef __ZS_ATOMIC_COUNTER_HPP_INCLUDED__ +#define __ZS_ATOMIC_COUNTER_HPP_INCLUDED__ + +#include "stdint.hpp" +#include "platform.hpp" + +#if defined ZS_FORCE_MUTEXES +#define ZS_ATOMIC_COUNTER_MUTEX +#elif (defined __i386__ || defined __x86_64__) && defined __GNUC__ +#define ZS_ATOMIC_COUNTER_X86 +#elif 0 && defined __sparc__ && defined __GNUC__ +#define ZS_ATOMIC_COUNTER_SPARC +#elif defined ZS_HAVE_WINDOWS +#define ZS_ATOMIC_COUNTER_WINDOWS +#elif defined ZS_HAVE_SOLARIS +#define ZS_ATOMIC_COUNTER_SOLARIS +#else +#define ZS_ATOMIC_COUNTER_MUTEX +#endif + +#if defined ZS_ATOMIC_COUNTER_MUTEX +#include "mutex.hpp" +#elif defined ZS_ATOMIC_COUNTER_WINDOWS +#include "windows.hpp" +#elif defined ZS_ATOMIC_COUNTER_SOLARIS +#include <atomic.h> +#endif + +namespace zs +{ + + // This class represents an integer that can be incremented/decremented + // in atomic fashion. + + class atomic_counter_t + { + public: + + typedef uint32_t integer_t; + + inline atomic_counter_t (integer_t value_ = 0) : + value (value_) + { + } + + inline ~atomic_counter_t () + { + } + + // Set counter value (not thread-safe). + inline void set (integer_t value_) + { + value = value_; + } + + // Atomic addition. Returns the old value. + inline integer_t add (integer_t increment_) + { + integer_t old_value; + +#if defined ZS_ATOMIC_COUNTER_WINDOWS + old_value = InterlockedExchangeAdd ((LONG*) &value, increment_); +#elif defined ZS_ATOMIC_COUNTER_SOLARIS + integer_t new_value = atomic_add_32_nv (&value, increment_); + old_value = new_value - increment_; +#elif defined ZS_ATOMIC_COUNTER_X86 + __asm__ volatile ( + "lock; xadd %0, %1 \n\t" + : "=r" (old_value), "=m" (value) + : "0" (increment_), "m" (value) + : "cc", "memory"); +#elif defined ZS_ATOMIC_COUNTER_SPARC + integer_t tmp; + __asm__ volatile ( + "ld [%4], %0 \n\t" + "1: \n\t" + "add %0, %3, %1 \n\t" + "cas [%4], %0, %1 \n\t" + "cmp %0, %1 \n\t" + "bne,a,pn %%icc, 1b \n\t" + "mov %1, %0 \n\t" + : "=&r" (old_value), "=&r" (tmp), "=m" (value) + : "r" (increment_), "r" (&value) + : "cc", "memory"); +#elif defined ZS_ATOMIC_COUNTER_MUTEX + sync.lock (); + old_value = value; + value += increment_; + sync.unlock (); +#else +#error +#endif + return old_value; + } + + // Atomic subtraction. Returns false if the counter drops to zero. + inline bool sub (integer_t decrement) + { +#if defined ZS_ATOMIC_COUNTER_WINDOWS + LONG delta = - ((LONG) decrement); + integer_t old = InterlockedExchangeAdd ((LONG*) &value, delta); + return old - decrement != 0; +#elif defined ZS_ATOMIC_COUNTER_SOLARIS + int32_t delta = - ((int32_t) decrement); + integer_t nv = atomic_add_32_nv (&value, delta); + return nv != 0; +#elif defined ZS_ATOMIC_COUNTER_X86 + integer_t oldval = -decrement; + volatile integer_t *val = &value; + __asm__ volatile ("lock; xaddl %0,%1" + : "=r" (oldval), "=m" (*val) + : "0" (oldval), "m" (*val) + : "cc"); + return oldval != decrement; +#elif defined ZS_ATOMIC_COUNTER_SPARC + volatile integer_t *val = &value; + integer_t tmp; + integer_t result; + __asm__ volatile( + "ld [%4], %1\n\t" + "1:\n\t" + "add %1, %0, %2\n\t" + "cas [%4], %1, %2\n\t" + "cmp %1, %2\n\t" + "bne,a,pn %%icc, 1b\n\t" + "mov %2, %1\n\t" + : "+r" (-decrement), "=&r" (tmp), "=&r" (result), "+m" (*val) + : "r" (val) + : "cc"); + return result <= decrement; +#elif defined ZS_ATOMIC_COUNTER_MUTEX + sync.lock (); + value -= decrement; + bool result = value ? true : false; + sync.unlock (); + return result; +#else +#error +#endif + } + + inline integer_t get () + { + return value; + } + + private: + + volatile integer_t value; +#if defined ZS_ATOMIC_COUNTER_MUTEX + mutex_t sync; +#endif + + atomic_counter_t (const atomic_counter_t&); + void operator = (const atomic_counter_t&); + }; + +} + +// Remove macros local to this file. +#if defined ZS_ATOMIC_COUNTER_WINDOWS +#undef ZS_ATOMIC_COUNTER_WINDOWS +#endif +#if defined ZS_ATOMIC_COUNTER_SOLARIS +#undef ZS_ATOMIC_COUNTER_SOLARIS +#endif +#if defined ZS_ATOMIC_COUNTER_X86 +#undef ZS_ATOMIC_COUNTER_X86 +#endif +#if defined ZS_ATOMIC_COUNTER_SPARC +#undef ZS_ATOMIC_COUNTER_SPARC +#endif +#if defined ZS_ATOMIC_COUNTER_MUTEX +#undef ZS_ATOMIC_COUNTER_MUTEX +#endif + +#endif diff --git a/src/atomic_ptr.hpp b/src/atomic_ptr.hpp new file mode 100644 index 0000000..fcc4e73 --- /dev/null +++ b/src/atomic_ptr.hpp @@ -0,0 +1,189 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef __ZS_ATOMIC_PTR_HPP_INCLUDED__ +#define __ZS_ATOMIC_PTR_HPP_INCLUDED__ + +#include "platform.hpp" + +#if defined ZS_FORCE_MUTEXES +#define ZS_ATOMIC_PTR_MUTEX +#elif (defined __i386__ || defined __x86_64__) && defined __GNUC__ +#define ZS_ATOMIC_PTR_X86 +#elif 0 && defined __sparc__ && defined __GNUC__ +#define ZS_ATOMIC_PTR_SPARC +#elif defined ZS_HAVE_WINDOWS +#define ZS_ATOMIC_PTR_WINDOWS +#elif defined ZS_HAVE_SOLARIS +#define ZS_ATOMIC_PTR_SOLARIS +#else +#define ZS_ATOMIC_PTR_MUTEX +#endif + +#if defined ZS_ATOMIC_PTR_MUTEX +#include "mutex.hpp" +#elif defined ZS_ATOMIC_PTR_WINDOWS +#include "windows.hpp" +#elif defined ZS_ATOMIC_PTR_SOLARIS +#include <atomic.h> +#endif + +namespace zs +{ + + // This class encapsulates several atomic operations on pointers. + + template <typename T> class atomic_ptr_t + { + public: + + // Initialise atomic pointer + inline atomic_ptr_t () + { + ptr = NULL; + } + + // Destroy atomic pointer + inline ~atomic_ptr_t () + { + } + + // Set value of atomic pointer in a non-threadsafe way + // Use this function only when you are sure that at most one + // thread is accessing the pointer at the moment. + inline void set (T *ptr_) + { + this->ptr = ptr_; + } + + // Perform atomic 'exchange pointers' operation. Pointer is set + // to the 'val' value. Old value is returned. + inline T *xchg (T *val_) + { +#if defined ZS_ATOMIC_PTR_WINDOWS + return (T*) InterlockedExchangePointer (&ptr, val_); +#elif defined ZS_ATOMIC_PTR_SOLARIS + return (T*) atomic_swap_ptr (&ptr, val_); +#elif defined ZS_ATOMIC_PTR_X86 + T *old; + __asm__ volatile ( + "lock; xchg %0, %2" + : "=r" (old), "=m" (ptr) + : "m" (ptr), "0" (val_)); + return old; +#elif defined ZS_ATOMIC_PTR_SPARC + T* newptr = val_; + volatile T** ptrin = &ptr; + T* tmp; + T* prev; + __asm__ __volatile__( + "ld [%4], %1\n\t" + "1:\n\t" + "mov %0, %2\n\t" + "cas [%4], %1, %2\n\t" + "cmp %1, %2\n\t" + "bne,a,pn %%icc, 1b\n\t" + "mov %2, %1\n\t" + : "+r" (newptr), "=&r" (tmp), "=&r" (prev), "+m" (*ptrin) + : "r" (ptrin) + : "cc"); + return prev; +#elif defined ZS_ATOMIC_PTR_MUTEX + sync.lock (); + T *old = (T*) ptr; + ptr = val_; + sync.unlock (); + return old; +#else +#error +#endif + } + + // Perform atomic 'compare and swap' operation on the pointer. + // The pointer is compared to 'cmp' argument and if they are + // equal, its value is set to 'val'. Old value of the pointer + // is returned. + inline T *cas (T *cmp_, T *val_) + { +#if defined ZS_ATOMIC_PTR_WINDOWS + return (T*) InterlockedCompareExchangePointer ( + (volatile PVOID*) &ptr, val_, cmp_); +#elif defined ZS_ATOMIC_PTR_SOLARIS + return (T*) atomic_cas_ptr (&ptr, cmp_, val_); +#elif defined ZS_ATOMIC_PTR_X86 + T *old; + __asm__ volatile ( + "lock; cmpxchg %2, %3" + : "=a" (old), "=m" (ptr) + : "r" (val_), "m" (ptr), "0" (cmp_) + : "cc"); + return old; +#elif defined ZS_ATOMIC_PTR_SPARC + volatile T** ptrin = &ptr; + volatile T* prev = ptr; + __asm__ __volatile__( + "cas [%3], %1, %2\n\t" + : "+m" (*ptrin) + : "r" (cmp_), "r" (val_), "r" (ptrin) + : "cc"); + return prev; +#elif defined ZS_ATOMIC_PTR_MUTEX + sync.lock (); + T *old = (T*) ptr; + if (ptr == cmp_) + ptr = val_; + sync.unlock (); + return old; +#else +#error +#endif + } + + private: + + volatile T *ptr; +#if defined ZS_ATOMIC_PTR_MUTEX + mutex_t sync; +#endif + + atomic_ptr_t (const atomic_ptr_t&); + void operator = (const atomic_ptr_t&); + }; + +} + +// Remove macros local to this file. +#if defined ZS_ATOMIC_PTR_WINDOWS +#undef ZS_ATOMIC_PTR_WINDOWS +#endif +#if defined ZS_ATOMIC_PTR_SOLARIS +#undef ZS_ATOMIC_PTR_SOLARIS +#endif +#if defined ZS_ATOMIC_PTR_X86 +#undef ZS_ATOMIC_PTR_X86 +#endif +#if defined ZS_ATOMIC_PTR_SPARC +#undef ZS_ATOMIC_PTR_SPARC +#endif +#if defined ZS_ATOMIC_PTR_MUTEX +#undef ZS_ATOMIC_PTR_MUTEX +#endif + +#endif diff --git a/src/command.hpp b/src/command.hpp new file mode 100644 index 0000000..0553137 --- /dev/null +++ b/src/command.hpp @@ -0,0 +1,98 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_COMMAND_HPP_INCLUDED__ +#define __ZS_COMMAND_HPP_INCLUDED__ + +#include "stdint.hpp" + +namespace zs +{ + + // This structure defines the commands that can be sent between threads. + + struct command_t + { + // Object to process the command. + class object_t *destination; + + enum type_t + { + stop, + bind, + head, + tail, + reg, + reg_and_bind, + unreg, + engine, + terminate, + terminate_ack + } type; + + union { + + struct { + } stop; + + struct { + class pipe_reader_t *reader; + class session_t *peer; + } bind; + + struct { + uint64_t bytes; + } tail; + + struct { + uint64_t bytes; + } head; + + struct { + class simple_semaphore_t *smph; + } reg; + + struct { + class session_t *peer; + bool flow_in; + bool flow_out; + } reg_and_bind; + + struct { + class simple_semaphore_t *smph; + } unreg; + + // TODO: Engine object won't be deallocated on terminal shutdown + // while the command is still on the fly! + struct { + class i_engine *engine; + } engine; + + struct { + } terminate; + + struct { + } terminate_ack; + + } args; + }; + +} + +#endif diff --git a/src/config.hpp b/src/config.hpp new file mode 100644 index 0000000..a0569ea --- /dev/null +++ b/src/config.hpp @@ -0,0 +1,71 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_CONFIG_HPP_INCLUDED__ +#define __ZS_CONFIG_HPP_INCLUDED__ + +namespace zs +{ + + // Compile-time settings. + + enum + { + + // Number of new messages in message pipe needed to trigger new memory + // allocation. Setting this parameter to 256 decreases the impact of + // memory allocation by approximately 99.6% + message_pipe_granularity = 256, + + // Number of new commands in command pipe needed to trigger new memory + // allocation. The number should be kept low to decrease the memory + // footprint of dispatcher. + command_pipe_granularity = 4, + + // Maximal batching size for engines with receiving functionality. + // So, if there are 10 messages that fit into the batch size, all of + // them may be read by a single 'recv' system call, thus avoiding + // unnecessary network stack traversals. + in_batch_size = 8192, + + // Maximal batching size for engines with sending functionality. + // So, if there are 10 messages that fit into the batch size, all of + // them may be written by a single 'send' system call, thus avoiding + // unnecessary network stack traversals. + out_batch_size = 8192, + + // Maximum number of events the I/O thread can process in one go. + max_io_events = 256, + + // Maximal wait time for a timer (milliseconds). + max_timer_period = 100, + + // Maximal delay to process command in API thread (in CPU ticks). + // 3,000,000 ticks equals to 1 - 2 milliseconds on current CPUs. + max_command_delay = 3000000, + + // Maximal number of non-accepted connections that can be held by + // TCP listener object. + tcp_connection_backlog = 10 + + }; + +} + +#endif diff --git a/src/connecter.cpp b/src/connecter.cpp new file mode 100644 index 0000000..a21dde3 --- /dev/null +++ b/src/connecter.cpp @@ -0,0 +1,189 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "connecter.hpp" +#include "io_thread.hpp" +#include "session.hpp" +#include "err.hpp" +#include "simple_semaphore.hpp" +#include "zmq_tcp_engine.hpp" + +zs::connecter_t::connecter_t (io_thread_t *thread_, const char *addr_, + session_t *session_) : + io_object_t (thread_), + state (idle), + poller (NULL), + session (session_), + addr (addr_), + identity ("abcde"), + engine (NULL) +{ +} + +void zs::connecter_t::terminate () +{ + delete this; +} + +void zs::connecter_t::shutdown () +{ + delete this; +} + +zs::connecter_t::~connecter_t () +{ +} + +void zs::connecter_t::process_reg (simple_semaphore_t *smph_) +{ + // Fet poller pointer for further use. + zs_assert (!poller); + poller = get_poller (); + + // Ask the session to register itself with the I/O thread. Note that + // the session is living in the same I/O thread, thus this results + // in a synchronous call. + session->inc_seqnum (); + send_reg (session, NULL); + + // Unlock the application thread that created the connecter. + if (smph_) + smph_->post (); + + // Manually trigger timer event which will launch asynchronous connect. + state = waiting; + timer_event (); +} + +void zs::connecter_t::process_unreg (simple_semaphore_t *smph_) +{ + // Unregister connecter/engine from the poller. + zs_assert (poller); + if (state == connecting) + poller->rm_fd (handle); + else if (state == waiting) + poller->cancel_timer (this); + else if (state == sending) + engine->terminate (); + + // Unlock the application thread closing the connecter. + if (smph_) + smph_->post (); +} + +void zs::connecter_t::in_event () +{ + // Error occured in asynchronous connect. Retry to connect after a while. + if (state == connecting) { + fd_t fd = tcp_connecter.connect (); + zs_assert (fd == retired_fd); + poller->rm_fd (handle); + poller->add_timer (this); + state = waiting; + return; + } + + zs_assert (false); +} + +void zs::connecter_t::out_event () +{ + if (state == connecting) { + + fd_t fd = tcp_connecter.connect (); + if (fd == retired_fd) { + poller->rm_fd (handle); + poller->add_timer (this); + state = waiting; + return; + } + + poller->rm_fd (handle); + engine = new zmq_tcp_engine_t (fd); + zs_assert (engine); + engine->attach (poller, this); + state = sending; + return; + } + + zs_assert (false); +} + +void zs::connecter_t::timer_event () +{ + zs_assert (state == waiting); + + // Initiate async connect and start polling for its completion. If async + // connect fails instantly, try to reconnect after a while. + int rc = tcp_connecter.open (addr.c_str ()); + if (rc == 0) { + state = connecting; + in_event (); + } + else if (rc == 1) { + handle = poller->add_fd (tcp_connecter.get_fd (), this); + poller->set_pollout (handle); + state = connecting; + } + else { + poller->add_timer (this); + state = waiting; + } +} + +void zs::connecter_t::set_engine (struct i_engine *engine_) +{ + engine = engine_; +} + +bool zs::connecter_t::read (zs_msg *msg_) +{ + zs_assert (state == sending); + + // Deallocate old content of the message just in case. + zs_msg_close (msg_); + + // Send the identity. + zs_msg_init_size (msg_, identity.size ()); + memcpy (zs_msg_data (msg_), identity.c_str (), identity.size ()); + + // Ask engine to unregister from the poller. + i_engine *e = engine; + engine->detach (); + + // Attach the engine to the session. (Note that this is actually + // a synchronous call. + session->inc_seqnum (); + send_engine (session, e); + + state = idle; + + return true; +} + +bool zs::connecter_t::write (struct zs_msg *msg_) +{ + // No incoming messages are accepted till identity is sent. + return false; +} + +void zs::connecter_t::flush () +{ + // No incoming messages are accepted till identity is sent. +} diff --git a/src/connecter.hpp b/src/connecter.hpp new file mode 100644 index 0000000..91dbf17 --- /dev/null +++ b/src/connecter.hpp @@ -0,0 +1,99 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_CONNECTER_HPP_INCLUDED__ +#define __ZS_CONNECTER_HPP_INCLUDED__ + +#include <string> + +#include "../include/zs.h" + +#include "i_poller.hpp" +#include "io_object.hpp" +#include "i_poll_events.hpp" +#include "i_session.hpp" +#include "tcp_connecter.hpp" + +namespace zs +{ + + class connecter_t : public io_object_t, public i_poll_events, + public i_session + { + public: + + connecter_t (class io_thread_t *thread_, const char *addr_, + class session_t *session_); + + void terminate (); + void shutdown (); + + void process_reg (class simple_semaphore_t *smph_); + void process_unreg (class simple_semaphore_t *smph_); + + // i_poll_events implementation. + void in_event (); + void out_event (); + void timer_event (); + + // i_session implementation + void set_engine (struct i_engine *engine_); + // void shutdown (); + bool read (struct zs_msg *msg_); + bool write (struct zs_msg *msg_); + void flush (); + + private: + + // Clean-up. + ~connecter_t (); + + enum { + idle, + waiting, + connecting, + sending + } state; + + // Cached pointer to the poller. + struct i_poller *poller; + + // Handle of the connecting socket. + handle_t handle; + + // Associated session. It lives in the same I/O thread. + class session_t *session; + + // Address to connect to. + std::string addr; + + // Identity of the connection. + std::string identity; + + tcp_connecter_t tcp_connecter; + + struct i_engine *engine; + + connecter_t (const connecter_t&); + void operator = (const connecter_t&); + }; + +} + +#endif diff --git a/src/data_distributor.cpp b/src/data_distributor.cpp new file mode 100644 index 0000000..8f89c46 --- /dev/null +++ b/src/data_distributor.cpp @@ -0,0 +1,155 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "data_distributor.hpp" +#include "pipe_writer.hpp" +#include "err.hpp" +#include "session.hpp" +#include "msg.hpp" + +zs::data_distributor_t::data_distributor_t () : + session (NULL) +{ +} + +void zs::data_distributor_t::set_session (session_t *session_) +{ + zs_assert (!session); + session = session_; +} + +void zs::data_distributor_t::shutdown () +{ + // No need to deallocate pipes here. They'll be deallocated during the + // shutdown of the dispatcher. + delete this; +} + +void zs::data_distributor_t::terminate () +{ + // Pipe unregisters itself during the call to terminate, so the pipes + // list shinks by one in each iteration. + while (!pipes.empty ()) + pipes [0]->terminate (); + + delete this; +} + +zs::data_distributor_t::~data_distributor_t () +{ +} + +void zs::data_distributor_t::attach_pipe (pipe_writer_t *pipe_) +{ + // Associate demux with a new pipe. + pipe_->set_demux (this); + pipe_->set_index (pipes.size ()); + pipes.push_back (pipe_); +} + +void zs::data_distributor_t::detach_pipe (pipe_writer_t *pipe_) +{ + // Release the reference to the pipe. + int index = pipe_->get_index (); + pipe_->set_index (-1); + pipes [index] = pipes.back (); + pipes [index]->set_index (index); + pipes.pop_back (); +} + +bool zs::data_distributor_t::empty () +{ + return pipes.empty (); +} + +bool zs::data_distributor_t::send (zs_msg *msg_) +{ + int pipes_count = pipes.size (); + + // If there are no pipes available, simply drop the message. + if (pipes_count == 0) { + zs_msg_close (msg_); + zs_msg_init (msg_); + return true; + } + + // TODO: ??? + // First check whether all pipes are available for writing. +// for (pipes_t::iterator it = pipes.begin (); it != pipes.end (); it ++) +// if (!(*it)->check_write (msg_)) +// return false; + + // For VSMs the copying is straighforward. + if (msg_->content == (zs_msg_content*) ZS_VSM) { + for (pipes_t::iterator it = pipes.begin (); it != pipes.end (); it ++) + write_to_pipe (*it, msg_); + zs_msg_init (msg_); + return true; + } + + // Optimisation for the case when there's only a single pipe + // to send the message to - no refcount adjustment (i.e. atomic + // operations) needed. + if (pipes_count == 1) { + write_to_pipe (*pipes.begin (), msg_); + zs_msg_init (msg_); + return true; + } + + // There are at least 2 destinations for the message. That means we have + // to deal with reference counting. First add N-1 references to + // the content (we are holding one reference anyway, that's why the -1). + if (msg_->shared) + msg_->content->refcnt.add (pipes_count - 1); + else { + msg_->shared = true; + // TODO: Add memory barrier here. + msg_->content->refcnt.set (pipes_count); + } + + // Push the message to all destinations. + for (pipes_t::iterator it = pipes.begin (); it != pipes.end (); it ++) + write_to_pipe (*it, msg_); + + // Detach the original message from the data buffer. + zs_msg_init (msg_); + + return true; +} + +void zs::data_distributor_t::flush () +{ + // Flush all pipes. If there's large number of pipes, it can be pretty + // inefficient (especially if there's new message only in a single pipe). + // Can it be improved? + for (pipes_t::iterator it = pipes.begin (); it != pipes.end (); it ++) + (*it)->flush (); +} + +void zs::data_distributor_t::write_to_pipe (class pipe_writer_t *pipe_, + struct zs_msg *msg_) +{ + if (!pipe_->write (msg_)) { + // TODO: Push gap notification to the pipe. + zs_assert (false); + } +} + diff --git a/src/data_distributor.hpp b/src/data_distributor.hpp new file mode 100644 index 0000000..239de31 --- /dev/null +++ b/src/data_distributor.hpp @@ -0,0 +1,70 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_DATA_DISTRIBUTOR_HPP_INCLUDED__ +#define __ZS_DATA_DISTRIBUTOR_HPP_INCLUDED__ + +#include <vector> + +#include <i_demux.hpp> + +namespace zs +{ + + // Object to distribute messages to outbound pipes. + + class data_distributor_t : public i_demux + { + public: + + data_distributor_t (); + + // i_demux implementation. + void set_session (class session_t *session_); + void shutdown (); + void terminate (); + void attach_pipe (class pipe_writer_t *pipe_); + void detach_pipe (class pipe_writer_t *pipe_); + bool empty (); + bool send (struct zs_msg *msg_); + void flush (); + + private: + + // Clean-up. + ~data_distributor_t (); + + // Reference to the owner session object. + class session_t *session; + + // Writes the message to the pipe if possible. If it isn't, writes + // a gap notification to the pipe. + void write_to_pipe (class pipe_writer_t *pipe_, struct zs_msg *msg_); + + // The list of outbound pipes. + typedef std::vector <class pipe_writer_t*> pipes_t; + pipes_t pipes; + + data_distributor_t (const data_distributor_t&); + void operator = (const data_distributor_t&); + }; + +} + +#endif diff --git a/src/decoder.hpp b/src/decoder.hpp new file mode 100644 index 0000000..c643df8 --- /dev/null +++ b/src/decoder.hpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_DECODER_HPP_INCLUDED__ +#define __ZS_DECODER_HPP_INCLUDED__ + +#include <stddef.h> +#include <string.h> +#include <algorithm> + +namespace zs +{ + + // Helper base class for decoders that know the amount of data to read + // in advance at any moment. Knowing the amount in advance is a property + // of the protocol used. Both AMQP and backend protocol are based on + // size-prefixed paradigm, therefore they are using decoder_t to parse + // the messages. On the other hand, XML-based transports (like XMPP or + // SOAP) don't allow for knowing the size of data to read in advance and + // should use different decoding algorithms. + // + // Decoder implements the state machine that parses the incoming buffer. + // Derived class should implement individual state machine actions. + + template <typename T> class decoder_t + { + public: + + inline decoder_t () : + read_ptr (NULL), + to_read (0), + next (NULL) + { + } + + // Push the binary data to the decoder. Returns number of bytes + // actually parsed. + inline size_t write (unsigned char *data_, size_t size_) + { + size_t pos = 0; + while (true) { + size_t to_copy = std::min (to_read, size_ - pos); + if (read_ptr) { + memcpy (read_ptr, data_ + pos, to_copy); + read_ptr += to_copy; + } + pos += to_copy; + to_read -= to_copy; + while (!to_read) + if (!(static_cast <T*> (this)->*next) ()) + return pos; + if (pos == size_) + return pos; + } + } + + protected: + + // Prototype of state machine action. Action should return false if + // it is unable to push the data to the system. + typedef bool (T::*step_t) (); + + // This function should be called from derived class to read data + // from the buffer and schedule next state machine action. + inline void next_step (void *read_ptr_, size_t to_read_, + step_t next_) + { + read_ptr = (unsigned char*) read_ptr_; + to_read = to_read_; + next = next_; + } + + private: + + unsigned char *read_ptr; + size_t to_read; + step_t next; + + decoder_t (const decoder_t&); + void operator = (const decoder_t&); + }; + +} + +#endif diff --git a/src/devpoll.cpp b/src/devpoll.cpp new file mode 100644 index 0000000..6e3a8c1 --- /dev/null +++ b/src/devpoll.cpp @@ -0,0 +1,224 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "platform.hpp" + +#if defined ZS_HAVE_SOLARIS || defined ZS_HAVE_HPUX + +#include <sys/devpoll.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> + +#include "devpoll.hpp" +#include "err.hpp" +#include "config.hpp" + +zs::devpoll_t::devpoll_t () +{ + // Get limit on open files + struct rlimit rl; + int rc = getrlimit (RLIMIT_NOFILE, &rl); + errno_assert (rc != -1); + fd_table.resize (rl.rlim_cur); + + for (rlim_t i = 0; i < rl.rlim_cur; i ++) + fd_table [i].valid = false; + + devpoll_fd = open ("/dev/poll", O_RDWR); + errno_assert (devpoll_fd != -1); +} + +zs::devpoll_t::~devpoll_t () +{ + close (devpoll_fd); +} + +void zs::devpoll_t::devpoll_ctl (fd_t fd_, short events_) +{ + struct pollfd pfd = {fd_, events_, 0}; + ssize_t rc = write (devpoll_fd, &pfd, sizeof pfd); + zs_assert (rc == sizeof pfd); +} + +zs::handle_t zs::devpoll_t::add_fd (fd_t fd_, i_poll_events *reactor_) +{ + assert (!fd_table [fd_].valid); + + fd_table [fd_].events = 0; + fd_table [fd_].reactor = reactor_; + fd_table [fd_].valid = true; + fd_table [fd_].accepted = false; + + devpoll_ctl (fd_, 0); + pending_list.push_back (fd_); + + // Increase the load metric of the thread. + load.add (1); + + handle_t handle; + handle.fd = fd_; + return handle; +} + +void zs::devpoll_t::rm_fd (handle_t handle_) +{ + assert (fd_table [handle_.fd].valid); + + devpoll_ctl (handle_.fd, POLLREMOVE); + fd_table [handle_.fd].valid = false; + + // Decrease the load metric of the thread. + load.sub (1); +} + +void zs::devpoll_t::set_pollin (handle_t handle_) +{ + fd_t fd = handle_.fd; + devpoll_ctl (fd, POLLREMOVE); + fd_table [fd].events |= POLLIN; + devpoll_ctl (fd, fd_table [fd].events); +} + +void zs::devpoll_t::reset_pollin (handle_t handle_) +{ + fd_t fd = handle_.fd; + devpoll_ctl (fd, POLLREMOVE); + fd_table [fd].events &= ~((short) POLLIN); + devpoll_ctl (fd, fd_table [fd].events); +} + +void zs::devpoll_t::set_pollout (handle_t handle_) +{ + fd_t fd = handle_.fd; + devpoll_ctl (fd, POLLREMOVE); + fd_table [fd].events |= POLLOUT; + devpoll_ctl (fd, fd_table [fd].events); +} + +void zs::devpoll_t::reset_pollout (handle_t handle_) +{ + fd_t fd = handle_.fd; + devpoll_ctl (fd, POLLREMOVE); + fd_table [fd].events &= ~((short) POLLOUT); + devpoll_ctl (fd, fd_table [fd].events); +} + +void zs::devpoll_t::add_timer (i_poll_events *events_) +{ + timers.push_back (events_); +} + +void zs::devpoll_t::cancel_timer (i_poll_events *events_) +{ + timers_t::iterator it = std::find (timers.begin (), timers.end (), events_); + if (it != timers.end ()) + timers.erase (it); +} + +int zs::devpoll_t::get_load () +{ + return load.get (); +} + +void zs::devpoll_t::start () +{ + worker.start (worker_routine, this); +} + +void zs::devpoll_t::stop () +{ + stopping = true; +} + +void zs::devpoll_t::join () +{ + worker.stop (); +} + +bool zs::devpoll_t::loop () +{ + // According to the poll(7d) man page, we can retrieve + // no more then (OPEN_MAX - 1) events. + int nfds = std::min (max_io_events, OPEN_MAX - 1); + + while (!stopping) { + + struct pollfd ev_buf [max_io_events]; + struct dvpoll poll_req; + + for (pending_list_t::size_type i = 0; i < pending_list.size (); i ++) + fd_table [pending_list [i]].accepted = true; + pending_list.clear (); + + poll_req.dp_fds = &ev_buf [0]; + poll_req.dp_nfds = nfds; + poll_req.dp_timeout = timers.empty () ? -1 : max_timer_period; + + // Wait for events. + int n = ioctl (devpoll_fd, DP_POLL, &poll_req); + if (n == -1 && errno == EINTR) + continue; + errno_assert (n != -1); + + // Handle timer. + if (!n) { + + // Use local list of timers as timer handlers may fill new timers + // into the original array. + timers_t t; + std::swap (timers, t); + + // Trigger all the timers. + for (timers_t::iterator it = t.begin (); it != t.end (); it ++) + (*it)->timer_event (); + + continue; + } + + for (int i = 0; i < n; i ++) { + + fd_entry_t *fd_ptr = &fd_table [ev_buf [i].fd]; + if (!fd_ptr->valid || !fd_ptr->accepted) + continue; + if (ev_buf [i].revents & (POLLERR | POLLHUP)) + fd_ptr->reactor->in_event (); + if (!fd_ptr->valid || !fd_ptr->accepted) + continue; + if (ev_buf [i].revents & POLLOUT) + fd_ptr->reactor->out_event (); + if (!fd_ptr->valid || !fd_ptr->accepted) + continue; + if (ev_buf [i].revents & POLLIN) + fd_ptr->reactor->in_event (); + } + } +} + +void zs::devpoll_t::worker_routine (void *arg_) +{ + ((devpoll_t*) arg_)->loop (); +} + +#endif diff --git a/src/devpoll.hpp b/src/devpoll.hpp new file mode 100644 index 0000000..56b3b25 --- /dev/null +++ b/src/devpoll.hpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_DEVPOLL_HPP_INCLUDED__ +#define __ZS_DEVPOLL_HPP_INCLUDED__ + +#include "platform.hpp" + +#if defined ZS_HAVE_SOLARIS || ZS_HAVE_HPUX + +#include <vector> + +#include "i_poller.hpp" +#include "fd.hpp" +#include "thread.hpp" +#include "atomic_counter.hpp" + +namespace zs +{ + + // Implements socket polling mechanism using the Solaris-specific + // "/dev/poll" interface. + + class devpoll_t : public i_poller + { + public: + + devpoll_t (); + virtual ~devpoll_t (); + + // i_poller implementation. + handle_t add_fd (fd_t fd_, i_poll_events *events_); + void rm_fd (handle_t handle_); + void set_pollin (handle_t handle_); + void reset_pollin (handle_t handle_); + void set_pollout (handle_t handle_); + void reset_pollout (handle_t handle_); + void add_timer (i_poll_events *events_); + void cancel_timer (i_poll_events *events_); + int get_load (); + void start (); + void stop (); + void join (); + + private: + + // Main worker thread routine. + static void worker_routine (void *arg_); + + // Main event loop. + void loop (); + + // File descriptor referring to "/dev/poll" pseudo-device. + fd_t devpoll_fd; + + struct fd_entry_t + { + short events; + struct i_poll_events *reactor; + bool valid; + bool accepted; + }; + + std::vector <fd_entry_t> fd_table; + + typedef std::vector <fd_t> pending_list_t; + pending_list_t pending_list; + + // Pollset manipulation function. + void devpoll_ctl (fd_t fd_, short events_); + + // List of all the engines waiting for the timer event. + typedef std::vector <struct i_poll_events*> timers_t; + timers_t timers; + + // If true, thread is in the process of shutting down. + bool stopping; + + // Handle of the physical thread doing the I/O work. + thread_t worker; + + // Load of the poller. Currently number of file descriptors + // registered with the poller. + atomic_counter_t load; + + devpoll_t (const devpoll_t&); + void operator = (const devpoll_t&); + }; + +} + +#endif + +#endif diff --git a/src/dispatcher.cpp b/src/dispatcher.cpp new file mode 100644 index 0000000..c468857 --- /dev/null +++ b/src/dispatcher.cpp @@ -0,0 +1,266 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "dispatcher.hpp" +#include "app_thread.hpp" +#include "io_thread.hpp" +#include "platform.hpp" +#include "err.hpp" +#include "pipe.hpp" +#include "pipe_reader.hpp" +#include "pipe_writer.hpp" +#include "session.hpp" +#include "i_api.hpp" + +#if defined ZS_HAVE_WINDOWS +#include "windows.h" +#endif + +zs::dispatcher_t::dispatcher_t (int app_threads_, int io_threads_) +{ +#ifdef ZS_HAVE_WINDOWS + // Intialise Windows sockets. Note that WSAStartup can be called multiple + // times given that WSACleanup will be called for each WSAStartup. + WORD version_requested = MAKEWORD (2, 2); + WSADATA wsa_data; + int rc = WSAStartup (version_requested, &wsa_data); + zs_assert (rc == 0); + zs_assert (LOBYTE (wsa_data.wVersion) == 2 && + HIBYTE (wsa_data.wVersion) == 2); +#endif + + // Create application thread proxies. + for (int i = 0; i != app_threads_; i++) { + app_thread_t *app_thread = new app_thread_t (this, i); + zs_assert (app_thread); + app_threads.push_back (app_thread); + signalers.push_back (app_thread->get_signaler ()); + } + + // Create I/O thread objects. + for (int i = 0; i != io_threads_; i++) { + io_thread_t *io_thread = new io_thread_t (this, i + app_threads_); + zs_assert (io_thread); + io_threads.push_back (io_thread); + signalers.push_back (io_thread->get_signaler ()); + } + + // Create command pipe matrix. + command_pipes = new command_pipe_t [signalers.size () * signalers.size ()]; + zs_assert (command_pipes); + + // Launch I/O threads. + for (int i = 0; i != io_threads_; i++) + io_threads [i]->start (); +} + +void zs::dispatcher_t::shutdown () +{ + delete this; +} + +zs::dispatcher_t::~dispatcher_t () +{ + // Ask I/O threads to terminate. + for (io_threads_t::size_type i = 0; i != io_threads.size (); i++) + io_threads [i]->stop (); + + // Wait till I/O threads actually terminate. + for (io_threads_t::size_type i = 0; i != io_threads.size (); i++) + io_threads [i]->join (); + + // At this point the current thread is the only thread with access to + // our internal data. Deallocation will be done exclusively in this thread. + for (app_threads_t::size_type i = 0; i != app_threads.size (); i++) + app_threads [i]->shutdown (); + for (io_threads_t::size_type i = 0; i != io_threads.size (); i++) + io_threads [i]->shutdown (); + + delete [] command_pipes; + + // Deallocate all the pipes, pipe readers and pipe writers. + for (pipes_t::iterator it = pipes.begin (); it != pipes.end (); it++) { + delete it->pipe; + delete it->reader; + delete it->writer; + } + +#ifdef ZS_HAVE_WINDOWS + // On Windows, uninitialise socket layer. + int rc = WSACleanup (); + wsa_assert (rc != SOCKET_ERROR); +#endif +} + +int zs::dispatcher_t::thread_slot_count () +{ + return signalers.size (); +} + +zs::i_api *zs::dispatcher_t::create_socket (int type_) +{ + threads_sync.lock (); + app_thread_t *thread = choose_app_thread (); + if (!thread) { + threads_sync.unlock (); + return NULL; + } + i_api *s = thread->create_socket (type_); + threads_sync.unlock (); + return s; +} + +zs::app_thread_t *zs::dispatcher_t::choose_app_thread () +{ + // Check whether thread ID is already assigned. If so, return it. + for (app_threads_t::size_type i = 0; i != app_threads.size (); i++) + if (app_threads [i]->is_current ()) + return app_threads [i]; + + // Check whether there's an unused thread slot in the dispatcher. + for (app_threads_t::size_type i = 0; i != app_threads.size (); i++) + if (app_threads [i]->make_current ()) + return app_threads [i]; + + // Thread limit was exceeded. + errno = EMFILE; + return NULL; +} + +zs::io_thread_t *zs::dispatcher_t::choose_io_thread (uint64_t taskset_) +{ + zs_assert (io_threads.size () > 0); + + // Find the I/O thread with minimum load. + int min_load = io_threads [0]->get_load (); + io_threads_t::size_type result = 0; + for (io_threads_t::size_type i = 1; i != io_threads.size (); i++) { + if (!taskset_ || (taskset_ & (uint64_t (1) << i))) { + int load = io_threads [i]->get_load (); + if (load < min_load) { + min_load = load; + result = i; + } + } + } + + return io_threads [result]; +} + +void zs::dispatcher_t::create_pipe (object_t *reader_parent_, + object_t *writer_parent_, uint64_t hwm_, uint64_t lwm_, + pipe_reader_t **reader_, pipe_writer_t **writer_) +{ + // Create the pipe, reader & writer triple. + pipe_t *pipe = new pipe_t; + zs_assert (pipe); + pipe_reader_t *reader = new pipe_reader_t (reader_parent_, pipe, + hwm_, lwm_); + zs_assert (reader); + pipe_writer_t *writer = new pipe_writer_t (writer_parent_, pipe, reader, + hwm_, lwm_); + zs_assert (writer); + reader->set_peer (writer); + + // Store the pipe in the repository. + pipe_info_t info = {pipe, reader, writer}; + pipes_sync.lock (); + pipe->set_index (pipes.size ()); + pipes.push_back (info); + pipes_sync.unlock (); + + *reader_ = reader; + *writer_ = writer; +} + +void zs::dispatcher_t::destroy_pipe (pipe_t *pipe_) +{ + // Remove the pipe from the repository. + pipe_info_t info; + pipes_sync.lock (); + pipes_t::size_type i = pipe_->get_index (); + info = pipes [i]; + pipes [i] = pipes.back (); + pipes.pop_back (); + pipes_sync.unlock (); + + // Deallocate the pipe and associated pipe reader & pipe writer. + zs_assert (info.pipe == pipe_); + delete info.pipe; + delete info.reader; + delete info.writer; +} + +int zs::dispatcher_t::register_inproc_endpoint (const char *endpoint_, + session_t *session_) +{ + inproc_endpoint_sync.lock (); + inproc_endpoints_t::iterator it = inproc_endpoints.find (endpoint_); + + if (it != inproc_endpoints.end ()) { + inproc_endpoint_sync.unlock (); + errno = EADDRINUSE; + return -1; + } + + inproc_endpoints.insert (std::make_pair (endpoint_, session_)); + + inproc_endpoint_sync.unlock (); + return 0; +} + +zs::object_t *zs::dispatcher_t::get_inproc_endpoint (const char *endpoint_) +{ + inproc_endpoint_sync.lock (); + inproc_endpoints_t::iterator it = inproc_endpoints.find (endpoint_); + + if (it == inproc_endpoints.end ()) { + inproc_endpoint_sync.unlock (); + errno = EADDRNOTAVAIL; + return NULL; + } + + it->second->inc_seqnum (); + object_t *session = it->second; + + inproc_endpoint_sync.unlock (); + return session; +} + +void zs::dispatcher_t::unregister_inproc_endpoints (session_t *session_) +{ + inproc_endpoint_sync.lock (); + + // Remove the connection from the repository. + // TODO: Yes, the algorithm has O(n^2) complexity. Should be O(log n). + for (inproc_endpoints_t::iterator it = inproc_endpoints.begin (); + it != inproc_endpoints.end ();) { + if (it->second == session_) { + inproc_endpoints.erase (it); + it = inproc_endpoints.begin (); + } + else + it++; + } + + inproc_endpoint_sync.unlock (); +} + diff --git a/src/dispatcher.hpp b/src/dispatcher.hpp new file mode 100644 index 0000000..05d2c49 --- /dev/null +++ b/src/dispatcher.hpp @@ -0,0 +1,170 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_DISPATCHER_HPP_INCLUDED__ +#define __ZS_DISPATCHER_HPP_INCLUDED__ + +#include <vector> +#include <map> +#include <string> + +#include "i_signaler.hpp" +#include "ypipe.hpp" +#include "command.hpp" +#include "config.hpp" +#include "mutex.hpp" +#include "stdint.hpp" + +namespace zs +{ + + // Dispatcher implements bidirectional thread-safe passing of commands + // between N threads. It consists of a ypipes to pass commands and + // signalers to wake up the receiver thread when new commands are + // available. Note that dispatcher is inefficient for passing messages + // within a thread (sender thread = receiver thread). The optimisation is + // not part of the class and should be implemented by individual threads + // (presumably by calling the command handling function directly). + + class dispatcher_t + { + public: + + // Create the dispatcher object. Matrix of pipes to communicate between + // each socket and each I/O thread is created along with appropriate + // signalers. + dispatcher_t (int app_threads_, int io_threads_); + + // To be called to terminate the whole infrastructure (zs_term). + void shutdown (); + + // Create a socket engine. + struct i_api *create_socket (int type_); + + // Returns number of thread slots in the dispatcher. To be used by + // individual threads to find out how many distinct signals can be + // received. + int thread_slot_count (); + + // Write command to the dispatcher. + inline void write (int source_, int destination_, + const command_t &command_) + { + command_pipe_t &pipe = + command_pipes [source_ * signalers.size () + destination_]; + pipe.write (command_); + if (!pipe.flush ()) + signalers [destination_]->signal (source_); + } + + // Read command from the dispatcher. Returns false if there is no + // command available. + inline bool read (int source_, int destination_, command_t *command_) + { + return command_pipes [source_ * signalers.size () + + destination_].read (command_); + } + + // Creates new pipe. + void create_pipe (class object_t *reader_parent_, + class object_t *writer_parent_, uint64_t hwm_, uint64_t lwm_, + class pipe_reader_t **reader_, class pipe_writer_t **writer_); + + // Deallocates the pipe. + void destroy_pipe (class pipe_t *pipe_); + + // Registers existing session object as an inproc endpoint. + int register_inproc_endpoint (const char *endpoint_, + class session_t *session_); + + // Retrieves an inproc endpoint. Increments the command sequence number + // of the object by one. Caller is thus bound to send the command + // to the connection after invoking this function. Returns NULL if + // the endpoint doesn't exist. + class object_t *get_inproc_endpoint (const char *endpoint_); + + // Removes all the inproc endpoints associated with the given session + // object from the global repository. + void unregister_inproc_endpoints (class session_t *session_); + + // Returns the I/O thread that is the least busy at the moment. + // Taskset specifies which I/O threads are eligible (0 = all). + class io_thread_t *choose_io_thread (uint64_t taskset_); + + private: + + // Clean-up. + ~dispatcher_t (); + + // Returns the app thread associated with the current thread. + // NULL if we are out of app thread slots. + class app_thread_t *choose_app_thread (); + + // Application threads. + typedef std::vector <class app_thread_t*> app_threads_t; + app_threads_t app_threads; + + // I/O threads. + typedef std::vector <class io_thread_t*> io_threads_t; + io_threads_t io_threads; + + // Signalers for both application and I/O threads. + std::vector <i_signaler*> signalers; + + // Pipe to hold the commands. + typedef ypipe_t <command_t, true, + command_pipe_granularity> command_pipe_t; + + // NxN matrix of command pipes. + command_pipe_t *command_pipes; + + // Synchronisation of accesses to shared thread data. + mutex_t threads_sync; + + // Global repository of pipes. It's used only on terminal shutdown + // to deallocate all the pipes irrespective of whether they are + // referenced from pipe_reader, pipe_writer or both. + struct pipe_info_t + { + class pipe_t *pipe; + class pipe_reader_t *reader; + class pipe_writer_t *writer; + }; + typedef std::vector <pipe_info_t> pipes_t; + pipes_t pipes; + + // Synchronisation of access to global repository of pipes. + mutex_t pipes_sync; + + // Global repository of available inproc endpoints. + typedef std::map <std::string, class session_t*> inproc_endpoints_t; + inproc_endpoints_t inproc_endpoints; + + // Synchronisation of access to the global repository + // of inproc endpoints. + mutex_t inproc_endpoint_sync; + + dispatcher_t (const dispatcher_t&); + void operator = (const dispatcher_t&); + }; + +} + +#endif + diff --git a/src/dummy_aggregator.cpp b/src/dummy_aggregator.cpp new file mode 100644 index 0000000..ef0cea6 --- /dev/null +++ b/src/dummy_aggregator.cpp @@ -0,0 +1,111 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "dummy_aggregator.hpp" +#include "err.hpp" +#include "pipe_reader.hpp" +#include "session.hpp" + +// Swaps pipes at specified indices. +#define swap_pipes(i1_, i2_) \ + std::swap (pipes [i1_], pipes [i2_]);\ + pipes [i1_]->set_index (i1_);\ + pipes [i2_]->set_index (i2_); + +zs::dummy_aggregator_t::dummy_aggregator_t () : + session (NULL), + pipe (NULL), + active (false) +{ +} + +void zs::dummy_aggregator_t::set_session (session_t *session_) +{ + zs_assert (!session); + session = session_; +} + +void zs::dummy_aggregator_t::shutdown () +{ + // No need to deallocate the pipe here. It'll be deallocated during the + // shutdown of the dispatcher. + delete this; +} + +void zs::dummy_aggregator_t::terminate () +{ + if (pipe) + pipe->terminate (); + + delete this; +} + +zs::dummy_aggregator_t::~dummy_aggregator_t () +{ +} + +void zs::dummy_aggregator_t::attach_pipe (pipe_reader_t *pipe_) +{ + zs_assert (!pipe); + pipe = pipe_; + active = true; + + // Associate new pipe with the mux object. + pipe_->set_mux (this); + session->revive (); +} + +void zs::dummy_aggregator_t::detach_pipe (pipe_reader_t *pipe_) +{ + zs_assert (pipe == pipe_); + deactivate (pipe_); + pipe = NULL; +} + +bool zs::dummy_aggregator_t::empty () +{ + return pipe == NULL; +} + +bool zs::dummy_aggregator_t::recv (zs_msg *msg_) +{ + // Deallocate old content of the message. + zs_msg_close (msg_); + + // Try to read from the pipe. + if (pipe && pipe->read (msg_)) + return true; + + // No message is available. Initialise the output parameter + // to be a 0-byte message. + zs_msg_init (msg_); + return false; +} + +void zs::dummy_aggregator_t::deactivate (pipe_reader_t *pipe_) +{ + active = false; +} + +void zs::dummy_aggregator_t::reactivate (pipe_reader_t *pipe_) +{ + active = true; +} diff --git a/src/dummy_aggregator.hpp b/src/dummy_aggregator.hpp new file mode 100644 index 0000000..ab5bcb9 --- /dev/null +++ b/src/dummy_aggregator.hpp @@ -0,0 +1,73 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_DUMMY_AGGREGATOR_HPP_INCLUDED__ +#define __ZS_DUMMY_AGGREGATOR_HPP_INCLUDED__ + +#include <vector> + +#include "i_mux.hpp" + +namespace zs +{ + + // Fake message aggregator. There can be at most one pipe bound to it, + // so there's no real aggregation going on. However, it is more efficient + // than a real aggregator. It's intended to be used in the contexts + // where business logic ensures there'll be at most one pipe bound. + + class dummy_aggregator_t : public i_mux + { + public: + + dummy_aggregator_t (); + + // i_mux interface implementation. + void set_session (session_t *session_); + void shutdown (); + void terminate (); + void attach_pipe (class pipe_reader_t *pipe_); + void detach_pipe (class pipe_reader_t *pipe_); + bool empty (); + void deactivate (class pipe_reader_t *pipe_); + void reactivate (class pipe_reader_t *pipe_); + bool recv (struct zs_msg *msg_); + + + private: + + // Clean-up. + ~dummy_aggregator_t (); + + // Reference to the owner session object. + class session_t *session; + + // The single pipe bound. + class pipe_reader_t *pipe; + + // If true, the pipe is active. + bool active; + + dummy_aggregator_t (const dummy_aggregator_t&); + void operator = (const dummy_aggregator_t&); + }; + +} + +#endif diff --git a/src/dummy_distributor.cpp b/src/dummy_distributor.cpp new file mode 100644 index 0000000..58cadfe --- /dev/null +++ b/src/dummy_distributor.cpp @@ -0,0 +1,85 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "dummy_distributor.hpp" +#include "pipe_writer.hpp" +#include "err.hpp" +#include "session.hpp" +#include "msg.hpp" + +zs::dummy_distributor_t::dummy_distributor_t () : + session (NULL) +{ +} + +void zs::dummy_distributor_t::set_session (session_t *session_) +{ + zs_assert (!session); + session = session_; +} + +void zs::dummy_distributor_t::shutdown () +{ + // No need to deallocate pipe here. It'll be deallocated during the + // shutdown of the dispatcher. + delete this; +} + +void zs::dummy_distributor_t::terminate () +{ + if (pipe) + pipe->terminate (); + + delete this; +} + +zs::dummy_distributor_t::~dummy_distributor_t () +{ +} + +void zs::dummy_distributor_t::attach_pipe (pipe_writer_t *pipe_) +{ + zs_assert (!pipe); + pipe = pipe_; +} + +void zs::dummy_distributor_t::detach_pipe (pipe_writer_t *pipe_) +{ + zs_assert (pipe == pipe_); + pipe = NULL; +} + +bool zs::dummy_distributor_t::empty () +{ + return pipe == NULL; +} + +bool zs::dummy_distributor_t::send (zs_msg *msg_) +{ + return pipe && pipe->write (msg_); +} + +void zs::dummy_distributor_t::flush () +{ + if (pipe) + pipe->flush (); +} + diff --git a/src/dummy_distributor.hpp b/src/dummy_distributor.hpp new file mode 100644 index 0000000..c200ad6 --- /dev/null +++ b/src/dummy_distributor.hpp @@ -0,0 +1,68 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_DUMMY_DISTRIBUTOR_HPP_INCLUDED__ +#define __ZS_DUMMY_DISTRIBUTOR_HPP_INCLUDED__ + +#include <vector> + +#include <i_demux.hpp> + +namespace zs +{ + + // Fake message distributor. There can be only one pipe bound to it + // so there no real distribution going on. However, it is more efficient + // than a real distributor and should be used where business logic + // ensures there'll be at most one pipe bound. + + class dummy_distributor_t : public i_demux + { + public: + + dummy_distributor_t (); + + // i_demux implementation. + void set_session (class session_t *session_); + void shutdown (); + void terminate (); + void attach_pipe (class pipe_writer_t *pipe_); + void detach_pipe (class pipe_writer_t *pipe_); + bool empty (); + bool send (struct zs_msg *msg_); + void flush (); + + private: + + // Clean-up. + ~dummy_distributor_t (); + + // Reference to the owner session object. + class session_t *session; + + // The bound pipe. + class pipe_writer_t *pipe; + + dummy_distributor_t (const dummy_distributor_t&); + void operator = (const dummy_distributor_t&); + }; + +} + +#endif diff --git a/src/encoder.hpp b/src/encoder.hpp new file mode 100644 index 0000000..1241873 --- /dev/null +++ b/src/encoder.hpp @@ -0,0 +1,108 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_ENCODER_HPP_INCLUDED__ +#define __ZS_ENCODER_HPP_INCLUDED__ + +#include <stddef.h> +#include <string.h> +#include <algorithm> + +namespace zs +{ + + // Helper base class for encoders. It implements the state machine that + // fills the outgoing buffer. Derived classes should implement individual + // state machine actions. + + template <typename T> class encoder_t + { + public: + + inline encoder_t () + { + } + + // The function tries to fill the supplied chunk by binary data. + // Returns the size of data actually filled in. If offset is not + // NULL, it is filled by offset of the first message in the batch. + // If there's no beginning of a message in the batch, offset is + // set to -1. + inline size_t read (unsigned char *data_, size_t size_, + int *offset_ = NULL) + { + int offset = -1; + size_t pos = 0; + + while (pos < size_) { + if (to_write) { + + size_t to_copy = std::min (to_write, size_ - pos); + memcpy (data_ + pos, write_pos, to_copy); + pos += to_copy; + write_pos += to_copy; + to_write -= to_copy; + } + else { + bool more = (static_cast <T*> (this)->*next) (); + if (beginning && offset == -1) { + offset = pos; + beginning = false; + } + if (!more) + break; + } + } + + if (offset_) + *offset_ = offset; + + return pos; + } + protected: + + // Prototype of state machine action. + typedef bool (T::*step_t) (); + + // This function should be called from derived class to write the data + // to the buffer and schedule next state machine action. Set beginning + // to true when you are writing first byte of a message. + inline void next_step (void *write_pos_, size_t to_write_, + step_t next_, bool beginning_) + { + write_pos = (unsigned char*) write_pos_; + to_write = to_write_; + next = next_; + beginning = beginning_; + } + + private: + + unsigned char *write_pos; + size_t to_write; + step_t next; + bool beginning; + + encoder_t (const encoder_t&); + void operator = (const encoder_t&); + }; + +} + +#endif diff --git a/src/epoll.cpp b/src/epoll.cpp new file mode 100644 index 0000000..a9780d2 --- /dev/null +++ b/src/epoll.cpp @@ -0,0 +1,214 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "platform.hpp" + +#ifdef ZS_HAVE_LINUX + +#include <sys/epoll.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <algorithm> + +#include "epoll.hpp" +#include "err.hpp" +#include "config.hpp" +#include "i_poll_events.hpp" + +zs::epoll_t::epoll_t () : + stopping (false) +{ + epoll_fd = epoll_create (1); + errno_assert (epoll_fd != -1); +} + +zs::epoll_t::~epoll_t () +{ + close (epoll_fd); + + for (retired_t::iterator it = retired.begin (); it != retired.end (); it ++) + delete *it; +} + +zs::handle_t zs::epoll_t::add_fd (fd_t fd_, i_poll_events *events_) +{ + poll_entry_t *pe = new poll_entry_t; + zs_assert (pe != NULL); + + // The memset is not actually needed. It's here to prevent debugging + // tools to complain about using uninitialised memory. + memset (pe, 0, sizeof (poll_entry_t)); + + pe->fd = fd_; + pe->ev.events = 0; + pe->ev.data.ptr = pe; + pe->events = events_; + + int rc = epoll_ctl (epoll_fd, EPOLL_CTL_ADD, fd_, &pe->ev); + errno_assert (rc != -1); + + // Increase the load metric of the thread. + load.add (1); + + handle_t handle; + handle.ptr = pe; + return handle; +} + +void zs::epoll_t::rm_fd (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + int rc = epoll_ctl (epoll_fd, EPOLL_CTL_DEL, pe->fd, &pe->ev); + errno_assert (rc != -1); + pe->fd = retired_fd; + retired.push_back (pe); + + // Decrease the load metric of the thread. + load.sub (1); +} + +void zs::epoll_t::set_pollin (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->ev.events |= EPOLLIN; + int rc = epoll_ctl (epoll_fd, EPOLL_CTL_MOD, pe->fd, &pe->ev); + errno_assert (rc != -1); +} + +void zs::epoll_t::reset_pollin (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->ev.events &= ~((short) EPOLLIN); + int rc = epoll_ctl (epoll_fd, EPOLL_CTL_MOD, pe->fd, &pe->ev); + errno_assert (rc != -1); +} + +void zs::epoll_t::set_pollout (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->ev.events |= EPOLLOUT; + int rc = epoll_ctl (epoll_fd, EPOLL_CTL_MOD, pe->fd, &pe->ev); + errno_assert (rc != -1); +} + +void zs::epoll_t::reset_pollout (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->ev.events &= ~((short) EPOLLOUT); + int rc = epoll_ctl (epoll_fd, EPOLL_CTL_MOD, pe->fd, &pe->ev); + errno_assert (rc != -1); +} + +void zs::epoll_t::add_timer (i_poll_events *events_) +{ + timers.push_back (events_); +} + +void zs::epoll_t::cancel_timer (i_poll_events *events_) +{ + timers_t::iterator it = std::find (timers.begin (), timers.end (), events_); + if (it == timers.end ()) + return; + timers.erase (it); +} + +int zs::epoll_t::get_load () +{ + return load.get (); +} + +void zs::epoll_t::start () +{ + worker.start (worker_routine, this); +} + +void zs::epoll_t::stop () +{ + stopping = true; +} + +void zs::epoll_t::join () +{ + worker.stop (); +} + +void zs::epoll_t::loop () +{ + epoll_event ev_buf [max_io_events]; + + while (!stopping) { + + // Wait for events. + int n; + while (true) { + n = epoll_wait (epoll_fd, &ev_buf [0], max_io_events, + timers.empty () ? -1 : max_timer_period); + if (!(n == -1 && errno == EINTR)) { + errno_assert (n != -1); + break; + } + } + + // Handle timer. + if (!n) { + + // Use local list of timers as timer handlers may fill new timers + // into the original array. + timers_t t; + std::swap (timers, t); + + // Trigger all the timers. + for (timers_t::iterator it = t.begin (); it != t.end (); it ++) + (*it)->timer_event (); + + continue; + } + + for (int i = 0; i < n; i ++) { + poll_entry_t *pe = ((poll_entry_t*) ev_buf [i].data.ptr); + + if (pe->fd == retired_fd) + continue; + if (ev_buf [i].events & (EPOLLERR | EPOLLHUP)) + pe->events->in_event (); + if (pe->fd == retired_fd) + continue; + if (ev_buf [i].events & EPOLLOUT) + pe->events->out_event (); + if (pe->fd == retired_fd) + continue; + if (ev_buf [i].events & EPOLLIN) + pe->events->in_event (); + } + + // Destroy retired event sources. + for (retired_t::iterator it = retired.begin (); it != retired.end (); + it ++) + delete *it; + retired.clear (); + } +} + +void zs::epoll_t::worker_routine (void *arg_) +{ + ((epoll_t*) arg_)->loop (); +} + +#endif diff --git a/src/epoll.hpp b/src/epoll.hpp new file mode 100644 index 0000000..a3f5168 --- /dev/null +++ b/src/epoll.hpp @@ -0,0 +1,107 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_EPOLL_HPP_INCLUDED__ +#define __ZS_EPOLL_HPP_INCLUDED__ + +#include "platform.hpp" + +#ifdef ZS_HAVE_LINUX + +#include <vector> +#include <sys/epoll.h> + +#include "i_poller.hpp" +//#include "i_poll_events.hpp" +#include "fd.hpp" +#include "thread.hpp" +#include "atomic_counter.hpp" + +namespace zs +{ + + // This class implements socket polling mechanism using the Linux-specific + // epoll mechanism. + + class epoll_t : public i_poller + { + public: + + epoll_t (); + virtual ~epoll_t (); + + // i_poller implementation. + handle_t add_fd (fd_t fd_, i_poll_events *events_); + void rm_fd (handle_t handle_); + void set_pollin (handle_t handle_); + void reset_pollin (handle_t handle_); + void set_pollout (handle_t handle_); + void reset_pollout (handle_t handle_); + void add_timer (i_poll_events *events_); + void cancel_timer (i_poll_events *events_); + int get_load (); + void start (); + void stop (); + void join (); + + private: + + // Main worker thread routine. + static void worker_routine (void *arg_); + + // Main event loop. + void loop (); + + // Main epoll file descriptor + fd_t epoll_fd; + + struct poll_entry_t + { + fd_t fd; + epoll_event ev; + struct i_poll_events *events; + }; + + // List of retired event sources. + typedef std::vector <poll_entry_t*> retired_t; + retired_t retired; + + // List of all the engines waiting for the timer event. + typedef std::vector <struct i_poll_events*> timers_t; + timers_t timers; + + // If true, thread is in the process of shutting down. + bool stopping; + + // Handle of the physical thread doing the I/O work. + thread_t worker; + + // Load of the poller. Currently number of file descriptors + // registered with the poller. + atomic_counter_t load; + + epoll_t (const epoll_t&); + void operator = (const epoll_t&); + }; + +} + +#endif + +#endif diff --git a/src/err.cpp b/src/err.cpp new file mode 100644 index 0000000..92a03ba --- /dev/null +++ b/src/err.cpp @@ -0,0 +1,146 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "err.hpp" +#include "platform.hpp" + +#ifdef ZS_HAVE_WINDOWS + +const char *zs::wsa_error() +{ + int errcode = WSAGetLastError (); + // TODO: This is not a generic way to handle this... + if (errcode == WSAEWOULDBLOCK) + return NULL; + + return + (errcode == WSABASEERR) ? + "No Error" : + (errcode == WSAEINTR) ? + "Interrupted system call" : + (errcode == WSAEBADF) ? + "Bad file number" : + (errcode == WSAEACCES) ? + "Permission denied" : + (errcode == WSAEFAULT) ? + "Bad address" : + (errcode == WSAEINVAL) ? + "Invalid argument" : + (errcode == WSAEMFILE) ? + "Too many open files" : + (errcode == WSAEWOULDBLOCK) ? + "Operation would block" : + (errcode == WSAEINPROGRESS) ? + "Operation now in progress" : + (errcode == WSAEALREADY) ? + "Operation already in progress" : + (errcode == WSAENOTSOCK) ? + "Socket operation on non-socket" : + (errcode == WSAEDESTADDRREQ) ? + "Destination address required" : + (errcode == WSAEMSGSIZE) ? + "Message too long" : + (errcode == WSAEPROTOTYPE) ? + "Protocol wrong type for socket" : + (errcode == WSAENOPROTOOPT) ? + "Bad protocol option" : + (errcode == WSAEPROTONOSUPPORT) ? + "Protocol not supported" : + (errcode == WSAESOCKTNOSUPPORT) ? + "Socket type not supported" : + (errcode == WSAEOPNOTSUPP) ? + "Operation not supported on socket" : + (errcode == WSAEPFNOSUPPORT) ? + "Protocol family not supported" : + (errcode == WSAEAFNOSUPPORT) ? + "Address family not supported by protocol family" : + (errcode == WSAEADDRINUSE) ? + "Address already in use" : + (errcode == WSAEADDRNOTAVAIL) ? + "Can't assign requested address" : + (errcode == WSAENETDOWN) ? + "Network is down" : + (errcode == WSAENETUNREACH) ? + "Network is unreachable" : + (errcode == WSAENETRESET) ? + "Net dropped connection or reset" : + (errcode == WSAECONNABORTED) ? + "Software caused connection abort" : + (errcode == WSAECONNRESET) ? + "Connection reset by peer" : + (errcode == WSAENOBUFS) ? + "No buffer space available" : + (errcode == WSAEISCONN) ? + "Socket is already connected" : + (errcode == WSAENOTCONN) ? + "Socket is not connected" : + (errcode == WSAESHUTDOWN) ? + "Can't send after socket shutdown" : + (errcode == WSAETOOMANYREFS) ? + "Too many references can't splice" : + (errcode == WSAETIMEDOUT) ? + "Connection timed out" : + (errcode == WSAECONNREFUSED) ? + "Connection refused" : + (errcode == WSAELOOP) ? + "Too many levels of symbolic links" : + (errcode == WSAENAMETOOLONG) ? + "File name too long" : + (errcode == WSAEHOSTDOWN) ? + "Host is down" : + (errcode == WSAEHOSTUNREACH) ? + "No Route to Host" : + (errcode == WSAENOTEMPTY) ? + "Directory not empty" : + (errcode == WSAEPROCLIM) ? + "Too many processes" : + (errcode == WSAEUSERS) ? + "Too many users" : + (errcode == WSAEDQUOT) ? + "Disc Quota Exceeded" : + (errcode == WSAESTALE) ? + "Stale NFS file handle" : + (errcode == WSAEREMOTE) ? + "Too many levels of remote in path" : + (errcode == WSASYSNOTREADY) ? + "Network SubSystem is unavailable" : + (errcode == WSAVERNOTSUPPORTED) ? + "WINSOCK DLL Version out of range" : + (errcode == WSANOTINITIALISED) ? + "Successful WSASTARTUP not yet performed" : + (errcode == WSAHOST_NOT_FOUND) ? + "Host not found" : + (errcode == WSATRY_AGAIN) ? + "Non-Authoritative Host not found" : + (errcode == WSANO_RECOVERY) ? + "Non-Recoverable errors: FORMERR REFUSED NOTIMP" : + (errcode == WSANO_DATA) ? + "Valid name no data record of requested" : + "error not defined"; +} +void zs::win_error (char *buffer_, size_t buffer_size_) +{ + DWORD errcode = GetLastError (); + DWORD rc = FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errcode, MAKELANGID(LANG_NEUTRAL, + SUBLANG_DEFAULT), buffer_, buffer_size_, NULL ); + zs_assert (rc); +} + +#endif diff --git a/src/err.hpp b/src/err.hpp new file mode 100644 index 0000000..657eb3d --- /dev/null +++ b/src/err.hpp @@ -0,0 +1,90 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_ERR_HPP_INCLUDED__ +#define __ZS_ERR_HPP_INCLUDED__ + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> + +#include "platform.hpp" + +#ifdef ZS_HAVE_WINDOWS +#include "windows.hpp" +#else +#include <netdb.h> +#endif + +#ifdef ZS_HAVE_WINDOWS + +namespace zs +{ + + const char *wsa_error (); + void win_error (char *buffer_, size_t buffer_size_); + +} + +// Provides convenient way to check WSA-style errors on Windows. +#define wsa_assert(x) do { if (!(x)){\ + const char *errstr = zs::wsa_error ();\ + if (errstr != NULL) {\ + fprintf (stderr, "Assertion failed: %s (%s:%d)\n", errstr, \ + __FILE__, __LINE__);\ + abort ();\ + }\ +}} while (false) + +// Provides convenient way to check GetLastError-style errors on Windows. +#define win_assert(x) do { if (!(x)) {\ + char errstr [256];\ + zs::win_error (errstr, 256);\ + fprintf (stderr, "Assertion failed: %s (%s:%d)\n", errstr, \ + __FILE__, __LINE__);\ + abort ();\ +}} while (false) + +#endif + +// This macro works in exactly the same way as the normal assert. It is used +// in its stead because standard assert on Win32 in broken - it prints nothing +// when used within the scope of JNI library. +#define zs_assert(x) do { if (!(x)){\ + fprintf (stderr, "Assertion failed: %s (%s:%d)\n", #x, \ + __FILE__, __LINE__);\ + abort ();\ +}} while (false) + +// Provides convenient way to check for errno-style errors. +#define errno_assert(x) do { if (!(x)) {\ + perror (NULL);\ + fprintf (stderr, "%s (%s:%d)\n", #x, __FILE__, __LINE__);\ + abort ();\ +}} while (false) + +// Provides convenient way to check for errors from getaddrinfo. +#define gai_assert(x) do { if (x) {\ + const char *errstr = gai_strerror (x);\ + fprintf (stderr, "%s (%s:%d)\n", errstr, __FILE__, __LINE__);\ + abort ();\ +}} while (false) + +#endif diff --git a/src/fair_aggregator.cpp b/src/fair_aggregator.cpp new file mode 100644 index 0000000..65bfac0 --- /dev/null +++ b/src/fair_aggregator.cpp @@ -0,0 +1,143 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "fair_aggregator.hpp" +#include "err.hpp" +#include "pipe_reader.hpp" +#include "session.hpp" + +// Swaps pipes at specified indices. +#define swap_pipes(i1_, i2_) \ + std::swap (pipes [i1_], pipes [i2_]);\ + pipes [i1_]->set_index (i1_);\ + pipes [i2_]->set_index (i2_); + +zs::fair_aggregator_t::fair_aggregator_t () : + session (NULL), + active (0), + current (0) +{ +} + +void zs::fair_aggregator_t::set_session (session_t *session_) +{ + zs_assert (!session); + session = session_; +} + +void zs::fair_aggregator_t::shutdown () +{ + // No need to deallocate pipes here. They'll be deallocated during the + // shutdown of the dispatcher. + delete this; +} + +void zs::fair_aggregator_t::terminate () +{ + // Pipe unregisters itself during the call to terminate, so the pipes + // list shinks by one in each iteration. + while (!pipes.empty ()) + pipes [0]->terminate (); + + delete this; +} + +zs::fair_aggregator_t::~fair_aggregator_t () +{ +} + +void zs::fair_aggregator_t::attach_pipe (pipe_reader_t *pipe_) +{ + // Associate new pipe with the mux object. + pipe_->set_mux (this); + pipes.push_back (pipe_); + active++; + if (pipes.size () > active) + swap_pipes (pipes.size () - 1, active - 1); + if (active == 1) + session->revive (); +} + +void zs::fair_aggregator_t::detach_pipe (pipe_reader_t *pipe_) +{ + // Move the pipe from the list of active pipes to the list of idle pipes. + deactivate (pipe_); + + // Move the pipe to the end of the idle list and remove it. + swap_pipes (pipe_->get_index (), pipes.size () - 1); + pipes.pop_back (); +} + +bool zs::fair_aggregator_t::empty () +{ + return pipes.empty (); +} + +bool zs::fair_aggregator_t::recv (zs_msg *msg_) +{ + // Deallocate old content of the message. + zs_msg_close (msg_); + + // O(1) fair queueing. Round-robin over the active pipes to get + // next message. + for (pipes_t::size_type i = active; i != 0; i--) { + + // Update current. + current = (current + 1) % active; + + // Try to read from current. + if (pipes [current]->read (msg_)) + return true; + } + + // No message is available. Initialise the output parameter + // to be a 0-byte message. + zs_msg_init (msg_); + return false; +} + +void zs::fair_aggregator_t::deactivate (pipe_reader_t *pipe_) +{ + int index = pipe_->get_index (); + + // Suspend an active pipe. + swap_pipes (index, active - 1); + active--; + + // If the deactiveted pipe is the current one, shift the current one pipe + // backwards so that the pipe that replaced the deactiveted one will be + // processed immediately rather than skipped. + if (index == (int) current) { + index--; + if (index == -1) + index = active - 1; + current = index; + } +} + +void zs::fair_aggregator_t::reactivate (pipe_reader_t *pipe_) +{ + // Revive an idle pipe. + swap_pipes (pipe_->get_index (), active); + active++; + if (active == 1) + session->revive (); +} diff --git a/src/fair_aggregator.hpp b/src/fair_aggregator.hpp new file mode 100644 index 0000000..9e6c3bb --- /dev/null +++ b/src/fair_aggregator.hpp @@ -0,0 +1,77 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_FAIR_AGGREGATOR_HPP_INCLUDED__ +#define __ZS_FAIR_AGGREGATOR_HPP_INCLUDED__ + +#include <vector> + +#include "i_mux.hpp" + +namespace zs +{ + + // Object to aggregate messages from inbound pipes. + + class fair_aggregator_t : public i_mux + { + public: + + fair_aggregator_t (); + + // i_mux interface implementation. + void set_session (session_t *session_); + void shutdown (); + void terminate (); + void attach_pipe (class pipe_reader_t *pipe_); + void detach_pipe (class pipe_reader_t *pipe_); + bool empty (); + void deactivate (class pipe_reader_t *pipe_); + void reactivate (class pipe_reader_t *pipe_); + bool recv (struct zs_msg *msg_); + + + private: + + // Clean-up. + ~fair_aggregator_t (); + + // Reference to the owner session object. + class session_t *session; + + // The list of inbound pipes. The active pipes are occupying indices + // from 0 to active-1. Suspended pipes occupy indices from 'active' + // to the end of the array. + typedef std::vector <class pipe_reader_t*> pipes_t; + pipes_t pipes; + + // The number of active pipes. + pipes_t::size_type active; + + // Pipe to retrieve next message from. The messages are retrieved + // from the pipes in round-robin fashion (a.k.a. fair queueing). + pipes_t::size_type current; + + fair_aggregator_t (const fair_aggregator_t&); + void operator = (const fair_aggregator_t&); + }; + +} + +#endif diff --git a/src/fd.hpp b/src/fd.hpp new file mode 100644 index 0000000..4d45ed2 --- /dev/null +++ b/src/fd.hpp @@ -0,0 +1,44 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_FD_HPP_INCLUDED__ +#define __ZS_FD_HPP_INCLUDED__ + +#include "platform.hpp" + +#ifdef ZS_HAVE_WINDOWS +#include "windows.hpp" +#endif + +namespace zs +{ +#ifdef _MSC_VER +#if (_MSC_VER <= 1400) + typedef UINT_PTR fd_t; + enum {retired_fd = (fd_t)(~0)} +#else + typedef SOCKET fd_t; + enum {retired_fd = INVALID_SOCKET}; +#endif +#else + typedef int fd_t; + enum {retired_fd = -1}; +#endif +} +#endif diff --git a/src/fd_signaler.cpp b/src/fd_signaler.cpp new file mode 100644 index 0000000..005dd86 --- /dev/null +++ b/src/fd_signaler.cpp @@ -0,0 +1,278 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "fd_signaler.hpp" +#include "platform.hpp" +#include "err.hpp" +#include "fd.hpp" + +#if defined ZS_HAVE_OPENVMS +#include <netinet/tcp.h> +#elif defined ZS_HAVE_WINDOWS +#include "windows.hpp" +#else +#include <unistd.h> +#include <fcntl.h> +#endif + +#if defined ZS_HAVE_EVENTFD + +#include <sys/eventfd.h> + +zs::fd_signaler_t::fd_signaler_t () +{ + // Create eventfd object. + fd = eventfd (0, 0); + errno_assert (fd != -1); + + // Set to non-blocking mode. + int flags = fcntl (fd, F_GETFL, 0); + if (flags == -1) + flags = 0; + int rc = fcntl (fd, F_SETFL, flags | O_NONBLOCK); + errno_assert (rc != -1); +} + +zs::fd_signaler_t::~fd_signaler_t () +{ + int rc = close (fd); + errno_assert (rc != -1); +} + +void zs::fd_signaler_t::signal (int signal_) +{ + zs_assert (signal_ >= 0 && signal_ < 64); + signals_t inc = 1; + inc <<= signal_; + ssize_t sz = write (fd, &inc, sizeof (signals_t)); + errno_assert (sz == sizeof (signals_t)); +} + +zs::fd_signaler_t::signals_t zs::fd_signaler_t::check () +{ + signals_t val; + ssize_t sz = read (fd, &val, sizeof (signals_t)); + if (sz == -1 && (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINTR)) + return 0; + errno_assert (sz != -1); + return val; +} + +zs::fd_t zs::fd_signaler_t::get_fd () +{ + return fd; +} + +#elif defined ZS_HAVE_WINDOWS + +zs::fd_signaler_t::fd_signaler_t () +{ + struct sockaddr_in addr; + SOCKET listener; + int addrlen = sizeof (addr); + + w = INVALID_SOCKET; + r = INVALID_SOCKET; + + fd_t rcs = (listener = socket (AF_INET, SOCK_STREAM, 0)); + wsa_assert (rcs != INVALID_SOCKET); + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + resolve_ip_hostname (&addr, "127.0.0.1:0"); + + int rc = bind (listener, (const struct sockaddr*) &addr, sizeof (addr)); + wsa_assert (rc != SOCKET_ERROR); + + rc = getsockname (listener, (struct sockaddr*) &addr, &addrlen); + wsa_assert (rc != SOCKET_ERROR); + + // Listen for incomming connections. + rc = listen (listener, 1); + wsa_assert (rc != SOCKET_ERROR); + + // Create the socket. + w = WSASocket (AF_INET, SOCK_STREAM, 0, NULL, 0, 0); + wsa_assert (w != INVALID_SOCKET); + + // Connect to the remote peer. + rc = connect (w, (sockaddr *) &addr, sizeof (addr)); + wsa_assert (rc != SOCKET_ERROR); + + // Accept connection from w + r = accept (listener, NULL, NULL); + wsa_assert (r != INVALID_SOCKET); + + rc = closesocket (listener); + wsa_assert (rc != SOCKET_ERROR); +} + +zs::fd_signaler_t::~fd_signaler_t () +{ + int rc = closesocket (w); + wsa_assert (rc != SOCKET_ERROR); + + rc = closesocket (r); + wsa_assert (rc != SOCKET_ERROR); +} + +void zs::fd_signaler_t::signal (int signal_) +{ + zs_assert (signal_ >= 0 && signal_ < 64); + char c = (char) signal_; + int rc = send (w, &c, 1, 0); + win_assert (rc != SOCKET_ERROR); +} + +zs::fd_signaler_t::signals_t zs::fd_signaler_t::check () +{ + char buffer [32]; + int nbytes = recv (r, buffer, 32, 0); + win_assert (nbytes != -1); + + signals_t signals = 0; + for (int pos = 0; pos != nbytes; pos++) { + zs_assert (buffer [pos] < 64); + signals |= (1 << (buffer [pos])); + } + return signals; +} + +zs::fd_t zs::fd_signaler_t::get_fd () +{ + return r; +} + +#else + +#include <sys/types.h> +#include <sys/socket.h> + +zs::fd_signaler_t::fd_signaler_t () +{ + int sv [2]; + int rc = socketpair (AF_UNIX, SOCK_STREAM, 0, sv); + errno_assert (rc == 0); + w = sv [0]; + r = sv [1]; + + // Set to non-blocking mode. + int flags = fcntl (r, F_GETFL, 0); + if (flags == -1) + flags = 0; + rc = fcntl (r, F_SETFL, flags | O_NONBLOCK); + errno_assert (rc != -1); +} + +zs::fd_signaler_t::~fd_signaler_t () +{ + close (w); + close (r); +} + +void zs::fd_signaler_t::signal (int signal_) +{ + zs_assert (signal_ >= 0 && signal_ < 64); + unsigned char c = (unsigned char) signal_; + ssize_t nbytes = send (w, &c, 1, 0); + errno_assert (nbytes == 1); +} + +zs::fd_signaler_t::signals_t zs::fd_signaler_t::check () +{ + unsigned char buffer [32]; + ssize_t nbytes = recv (r, buffer, 32, 0); + errno_assert (nbytes != -1); + signals_t signals = 0; + for (int pos = 0; pos != nbytes; pos ++) { + zs_assert (buffer [pos] < 64); + signals |= (1 << (buffer [pos])); + } + return signals; +} + +zs::fd_t zs::fd_signaler_t::get_fd () +{ + return r; +} + +#endif + +#if defined ZS_HAVE_OPENVMS + +int zs::fd_signaler_t::socketpair (int domain_, int type_, int protocol_, + int sv_ [2]) +{ + int listener; + sockaddr_in lcladdr; + socklen_t lcladdr_len; + int rc; + int on = 1; + + zs_assert (type_ == SOCK_STREAM); + + // Fill in the localhost address (127.0.0.1). + memset (&lcladdr, 0, sizeof (lcladdr)); + lcladdr.sin_family = AF_INET; + lcladdr.sin_addr.s_addr = 0x0100007f; + lcladdr.sin_port = INADDR_ANY; + + listener = socket (AF_INET, SOCK_STREAM, 0); + errno_assert (listener != -1); + + rc = setsockopt (listener, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on)); + errno_assert (rc != -1); + + rc = setsockopt (listener, IPPROTO_TCP, TCP_NODELACK, &on, sizeof (on)); + errno_assert (rc != -1); + + rc = bind(listener, (struct sockaddr*) &lcladdr, sizeof (lcladdr)); + errno_assert (rc != -1); + + lcladdr_len = sizeof (lcladdr); + + rc = getsockname (listener, (struct sockaddr*) &lcladdr, &lcladdr_len); + errno_assert (rc != -1); + + rc = listen (listener, 1); + errno_assert (rc != -1); + + sv_ [0] = socket (AF_INET, SOCK_STREAM, 0); + errno_assert (rc != -1); + + rc = setsockopt (sv_ [0], IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on)); + errno_assert (rc != -1); + + rc = setsockopt (sv_ [0], IPPROTO_TCP, TCP_NODELACK, &on, sizeof (on)); + errno_assert (rc != -1); + + rc = connect (sv_ [0], (struct sockaddr*) &lcladdr, sizeof (lcladdr)); + errno_assert (rc != -1); + + sv_ [1] = accept (listener, NULL, NULL); + errno_assert (sv_ [1] != -1); + + close (listener); + + return 0; +} + +#endif + diff --git a/src/fd_signaler.hpp b/src/fd_signaler.hpp new file mode 100644 index 0000000..34c5e8c --- /dev/null +++ b/src/fd_signaler.hpp @@ -0,0 +1,92 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_FD_SIGNALER_HPP_INCLUDED__ +#define __ZS_FD_SIGNALER_HPP_INCLUDED__ + +#include "platform.hpp" +#include "i_signaler.hpp" +#include "fd.hpp" +#include "stdint.hpp" + +namespace zs +{ + + // This object can be used to send individual signals from one thread to + // another. The specific of this pipe is that it has associated file + // descriptor and so it can be polled on. Same signal cannot be sent twice + // unless signals are retrieved by the reader side in the meantime. + + class fd_signaler_t : public i_signaler + { + public: + + typedef uint64_t signals_t; + + // Initialise the object. + fd_signaler_t (); + + // Destroy the object. + ~fd_signaler_t (); + + // Send specific signal. + void signal (int signal_); + + // Retrieves signals. Returns a set of signals in form of a bitmap. + // Signal with index 0 corresponds to value 1, index 1 to value 2, + // index 2 to value 4 etc. If there is no signal available, + // it returns zero immediately. + signals_t check (); + + // Get the file descriptor associated with the object. + fd_t get_fd (); + + private: + +#if defined ZMQ_HAVE_OPENVMS + + // Whilst OpenVMS supports socketpair - it maps to AF_INET only. + // Further, it does not set the socket options TCP_NODELAY and + // TCP_NODELACK which can lead to performance problems. We'll + // overload the socketpair function for this class. + // + // The bug will be fixed in V5.6 ECO4 and beyond. In the + // meantime, we'll create the socket pair manually. + static int socketpair (int domain_, int type_, int protocol_, + int sv_ [2]); + +#endif + +#if defined ZS_HAVE_EVENTFD + // Eventfd descriptor. + fd_t fd; +#else + // Write & read end of the socketpair. + fd_t w; + fd_t r; +#endif + + // Disable copying of fd_signeler object. + fd_signaler_t (const fd_signaler_t&); + void operator = (const fd_signaler_t&); + }; + +} + +#endif diff --git a/src/i_api.hpp b/src/i_api.hpp new file mode 100644 index 0000000..4dccd9e --- /dev/null +++ b/src/i_api.hpp @@ -0,0 +1,39 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_API_HPP_INCLUDED__ +#define __ZS_I_API_HPP_INCLUDED__ + +namespace zs +{ + + struct i_api + { + virtual int bind (const char *addr_, struct zs_opts *opts_) = 0; + virtual int connect (const char *addr_, struct zs_opts *opts_) = 0; + virtual int subscribe (const char *criteria_) = 0; + virtual int send (struct zs_msg *msg_, int flags_) = 0; + virtual int flush () = 0; + virtual int recv (struct zs_msg *msg_, int flags_) = 0; + virtual int close () = 0; + }; + +} + +#endif diff --git a/src/i_demux.hpp b/src/i_demux.hpp new file mode 100644 index 0000000..edded1e --- /dev/null +++ b/src/i_demux.hpp @@ -0,0 +1,56 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_DEMUX_HPP_INCLUDED__ +#define __ZS_I_DEMUX_HPP_INCLUDED__ + +namespace zs +{ + + struct i_demux + { + // Attaches mux to a particular session. + virtual void set_session (class session_t *session_) = 0; + + // To be called when the whole infrastrucure is being closed (zs_term). + virtual void shutdown () = 0; + + // To be called when session is being closed. + virtual void terminate () = 0; + + // Adds new pipe to the demux to send messages to. + virtual void attach_pipe (class pipe_writer_t *pipe_) = 0; + + // Removes pipe from the demux. + virtual void detach_pipe (class pipe_writer_t *pipe_) = 0; + + // Returns true if there's no pipe attached. + virtual bool empty () = 0; + + // Sends the message. Returns false if the message cannot be sent + // because the pipes are full. + virtual bool send (struct zs_msg *msg_) = 0; + + // Flushes messages downstream. + virtual void flush () = 0; + }; + +} + +#endif diff --git a/src/i_engine.hpp b/src/i_engine.hpp new file mode 100644 index 0000000..bade705 --- /dev/null +++ b/src/i_engine.hpp @@ -0,0 +1,53 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_ENGINE_HPP_INCLUDED__ +#define __ZS_I_ENGINE_HPP_INCLUDED__ + +namespace zs +{ + + // Generic interface to access engines from MD objects. + + struct i_engine + { + // Attach the engine with specified context. + virtual void attach (struct i_poller *poller_, + struct i_session *session_) = 0; + + // Detach the engine from the current context. + virtual void detach () = 0; + + // Notify the engine that new messages are available. + virtual void revive () = 0; + + // Called by session when it decides the engine + // should terminate itself. + virtual void schedule_terminate () = 0; + + // Called by normal object termination process. + virtual void terminate () = 0; + + // To be called by MD when terminal shutdown (zs_term) is in progress. + virtual void shutdown () = 0; + }; + +} + +#endif diff --git a/src/i_mux.hpp b/src/i_mux.hpp new file mode 100644 index 0000000..4c8ef72 --- /dev/null +++ b/src/i_mux.hpp @@ -0,0 +1,59 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_MUX_HPP_INCLUDED__ +#define __ZS_I_MUX_HPP_INCLUDED__ + +namespace zs +{ + + struct i_mux + { + // Attaches mux to a particular session. + virtual void set_session (class session_t *session_) = 0; + + // To be called when the whole infrastrucure is being closed (zs_term). + virtual void shutdown () = 0; + + // To be called when session is being closed. + virtual void terminate () = 0; + + // Adds new pipe to the mux to send messages to. + virtual void attach_pipe (class pipe_reader_t *pipe_) = 0; + + // Removes pipe from the mux. + virtual void detach_pipe (class pipe_reader_t *pipe_) = 0; + + // Returns true if there's no pipe attached. + virtual bool empty () = 0; + + // Shifts the pipe from active to passive state and vice versa. + // TODO: Check whether state transitions cannot be done by + // mux object itself without a need for external APIs. + virtual void deactivate (class pipe_reader_t *pipe_) = 0; + virtual void reactivate (class pipe_reader_t *pipe_) = 0; + + // Receives a message. Returns false when there is no message + // to receive. + virtual bool recv (struct zs_msg *msg_) = 0; + }; + +} + +#endif diff --git a/src/i_poll_events.hpp b/src/i_poll_events.hpp new file mode 100644 index 0000000..5189dad --- /dev/null +++ b/src/i_poll_events.hpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_POLL_EVENTS_HPP_INCLUDED__ +#define __ZS_I_POLL_EVENTS_HPP_INCLUDED__ + +namespace zs +{ + + // Virtual interface to be exposed by object that want to be notified + // about events on file descriptors. + + struct i_poll_events + { + virtual ~i_poll_events () {}; + + // Called by I/O thread when file descriptor is ready for reading. + virtual void in_event () = 0; + + // Called by I/O thread when file descriptor is ready for writing. + virtual void out_event () = 0; + + // Called when timer expires. + virtual void timer_event () = 0; + }; + +} + +#endif diff --git a/src/i_poller.hpp b/src/i_poller.hpp new file mode 100644 index 0000000..c226dfa --- /dev/null +++ b/src/i_poller.hpp @@ -0,0 +1,89 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_POLLER_HPP_INCLUDED__ +#define __ZS_I_POLLER_HPP_INCLUDED__ + +#include "fd.hpp" + +namespace zs +{ + + union handle_t + { + fd_t fd; + void *ptr; + }; + + // Virtual interface to be used when polling on file descriptors. + + struct i_poller + { + virtual ~i_poller () {}; + + // Add file descriptor to the polling set. Return handle + // representing the descriptor. 'events' interface will be used + // to invoke callback functions when event occurs. + virtual handle_t add_fd (fd_t fd_, struct i_poll_events *events_) = 0; + + // Remove file descriptor identified by handle from the polling set. + virtual void rm_fd (handle_t handle_) = 0; + + // Start polling for input from socket. + virtual void set_pollin (handle_t handle_) = 0; + + // Stop polling for input from socket. + virtual void reset_pollin (handle_t handle_) = 0; + + // Start polling for availability of the socket for writing. + virtual void set_pollout (handle_t handle_) = 0; + + // Stop polling for availability of the socket for writing. + virtual void reset_pollout (handle_t handle_) = 0; + + // Ask to be notified after some time. Actual interval varies between + // 0 and max_timer_period ms. Timer is destroyed once it expires or, + // optionally, when cancel_timer is called. + virtual void add_timer (struct i_poll_events *events_) = 0; + + // Cancel the timer set by add_timer method. + virtual void cancel_timer (struct i_poll_events *events_) = 0; + + // Returns load experienced by the I/O thread. Currently it's number + // of file descriptors handled by the poller, in the future we may + // use a metric taking actual traffic on the individual sockets into + // account. + virtual int get_load () = 0; + + // Start the execution of the underlying I/O thread. + // This method is called from a foreign thread. + virtual void start () = 0; + + // Ask underlying I/O thread to stop. This method is called from + // underlying thread (callback from io_thread object). + virtual void stop () = 0; + + // Wait for termination of undelying I/O thread. + // This method is called from a foreign thread. + virtual void join () = 0; + }; + +} + +#endif diff --git a/src/i_session.hpp b/src/i_session.hpp new file mode 100644 index 0000000..8a8c40f --- /dev/null +++ b/src/i_session.hpp @@ -0,0 +1,37 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_SESSION_HPP_INCLUDED__ +#define __ZS_I_SESSION_HPP_INCLUDED__ + +namespace zs +{ + + struct i_session + { + virtual void set_engine (struct i_engine *engine_) = 0; + virtual void shutdown () = 0; + virtual bool read (struct zs_msg *msg_) = 0; + virtual bool write (struct zs_msg *msg_) = 0; + virtual void flush () = 0; + }; + +} + +#endif diff --git a/src/i_signaler.hpp b/src/i_signaler.hpp new file mode 100644 index 0000000..f6f0398 --- /dev/null +++ b/src/i_signaler.hpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_SIGNALER_HPP_INCLUDED__ +#define __ZS_I_SIGNALER_HPP_INCLUDED__ + +namespace zs +{ + // Virtual interface used to send signals. Individual implementations + // may restrict the number of possible signal types to send. + + struct i_signaler + { + virtual ~i_signaler () {}; + + // Send a signal with a specific ID. + virtual void signal (int signal_) = 0; + }; + +} + +#endif diff --git a/src/i_thread.hpp b/src/i_thread.hpp new file mode 100644 index 0000000..fdb60c5 --- /dev/null +++ b/src/i_thread.hpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_I_THREAD_HPP_INCLUDED__ +#define __ZS_I_THREAD_HPP_INCLUDED__ + +namespace zs +{ + + // Interface used by session object to communicate with the thread + // it belongs to. + + struct i_thread + { + virtual void attach_session (class session_t *session_) = 0; + virtual void detach_session (class session_t *session_) = 0; + virtual struct i_poller *get_poller () = 0; + }; + +} + +#endif diff --git a/src/io_object.cpp b/src/io_object.cpp new file mode 100644 index 0000000..01388eb --- /dev/null +++ b/src/io_object.cpp @@ -0,0 +1,37 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "io_object.hpp" +#include "io_thread.hpp" +#include "i_poller.hpp" + +zs::io_object_t::io_object_t (io_thread_t *thread_) : + object_t (thread_), + thread (thread_) +{ +} + +zs::io_object_t::~io_object_t () +{ +} + +zs::i_poller *zs::io_object_t::get_poller () +{ + return thread->get_poller (); +} diff --git a/src/io_object.hpp b/src/io_object.hpp new file mode 100644 index 0000000..766c008 --- /dev/null +++ b/src/io_object.hpp @@ -0,0 +1,51 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_IO_OBJECT_HPP_INCLUDED__ +#define __ZS_IO_OBJECT_HPP_INCLUDED__ + +#include "object.hpp" + +namespace zs +{ + + // All objects running within the context of an I/O thread should be + // derived from this class to allow owning application threads to + // destroy them. + + class io_object_t : public object_t + { + public: + + io_object_t (class io_thread_t *thread_); + ~io_object_t (); + + virtual void terminate () = 0; + virtual void shutdown () = 0; + + struct i_poller *get_poller (); + + private: + + class io_thread_t *thread; + }; + +} + +#endif diff --git a/src/io_thread.cpp b/src/io_thread.cpp new file mode 100644 index 0000000..7994874 --- /dev/null +++ b/src/io_thread.cpp @@ -0,0 +1,177 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "io_thread.hpp" +#include "command.hpp" +#include "platform.hpp" +#include "err.hpp" +#include "command.hpp" +#include "epoll.hpp" +#include "poll.hpp" +#include "select.hpp" +#include "devpoll.hpp" +#include "kqueue.hpp" +#include "dispatcher.hpp" +#include "session.hpp" +#include "simple_semaphore.hpp" +#include "session.hpp" + +zs::io_thread_t::io_thread_t (dispatcher_t *dispatcher_, int thread_slot_) : + object_t (dispatcher_, thread_slot_) +{ +#if defined ZS_FORCE_SELECT + poller = new select_t; +#elif defined ZS_FORCE_POLL + poller = new poll_t; +#elif defined ZS_FORCE_EPOLL + poller = new epoll_t; +#elif defined ZS_FORCE_DEVPOLL + poller = new devpoll_t; +#elif defined ZS_FORCE_KQUEUE + poller = new kqueue_t; +#elif defined ZS_HAVE_LINUX + poller = new epoll_t; +#elif defined ZS_HAVE_WINDOWS + poller = new select_t; +#elif defined ZS_HAVE_FREEBSD + poller = new kqueue_t; +#elif defined ZS_HAVE_OPENBSD + poller = new kqueue_t; +#elif defined ZS_HAVE_SOLARIS + poller = new devpoll_t; +#elif defined ZS_HAVE_OSX + poller = new kqueue_t; +#elif defined ZS_HAVE_QNXNTO + poller = new poll_t; +#elif defined ZS_HAVE_AIX + poller = new poll_t; +#elif defined ZS_HAVE_HPUX + poller = new devpoll_t; +#elif defined ZS_HAVE_OPENVMS + poller = new select_t; +#else +#error Unsupported platform +#endif + zs_assert (poller); + + signaler_handle = poller->add_fd (signaler.get_fd (), this); + poller->set_pollin (signaler_handle); +} + +void zs::io_thread_t::shutdown () +{ + // Deallocate all the sessions associated with the thread. + while (!sessions.empty ()) + sessions [0]->shutdown (); + + delete this; +} + +zs::io_thread_t::~io_thread_t () +{ + delete poller; +} + +void zs::io_thread_t::start () +{ + // Start the underlying I/O thread. + poller->start (); +} + +void zs::io_thread_t::stop () +{ + send_stop (); +} + +void zs::io_thread_t::join () +{ + poller->join (); +} + +zs::i_signaler *zs::io_thread_t::get_signaler () +{ + return &signaler; +} + +int zs::io_thread_t::get_load () +{ + return poller->get_load (); +} + +void zs::io_thread_t::in_event () +{ + // Find out which threads are sending us commands. + fd_signaler_t::signals_t signals = signaler.check (); + zs_assert (signals); + + // Iterate through all the threads in the process and find out + // which of them sent us commands. + int slot_count = thread_slot_count (); + for (int source_thread_slot = 0; + source_thread_slot != slot_count; source_thread_slot++) { + if (signals & (fd_signaler_t::signals_t (1) << source_thread_slot)) { + + // Read all the commands from particular thread. + command_t cmd; + while (dispatcher->read (source_thread_slot, thread_slot, &cmd)) + cmd.destination->process_command (cmd); + } + } +} + +void zs::io_thread_t::out_event () +{ + // We are never polling for POLLOUT here. This function is never called. + zs_assert (false); +} + +void zs::io_thread_t::timer_event () +{ + // No timers here. This function is never called. + zs_assert (false); +} + +void zs::io_thread_t::attach_session (session_t *session_) +{ + session_->set_index (sessions.size ()); + sessions.push_back (session_); +} + +void zs::io_thread_t::detach_session (session_t *session_) +{ + // O(1) removal of the session from the list. + sessions_t::size_type i = session_->get_index (); + sessions [i] = sessions [sessions.size () - 1]; + sessions [i]->set_index (i); + sessions.pop_back (); +} + +zs::i_poller *zs::io_thread_t::get_poller () +{ + zs_assert (poller); + return poller; +} + +void zs::io_thread_t::process_stop () +{ + poller->rm_fd (signaler_handle); + poller->stop (); +} diff --git a/src/io_thread.hpp b/src/io_thread.hpp new file mode 100644 index 0000000..a57aa34 --- /dev/null +++ b/src/io_thread.hpp @@ -0,0 +1,99 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_IO_THREAD_HPP_INCLUDED__ +#define __ZS_IO_THREAD_HPP_INCLUDED__ + +#include <vector> + +#include "object.hpp" +#include "i_thread.hpp" +#include "i_poller.hpp" +#include "i_poll_events.hpp" +#include "fd_signaler.hpp" + +namespace zs +{ + + // Generic part of the I/O thread. Polling-mechanism-specific features + // are implemented in separate "polling objects". + + class io_thread_t : public object_t, public i_poll_events, public i_thread + { + public: + + io_thread_t (class dispatcher_t *dispatcher_, int thread_slot_); + + // Launch the physical thread. + void start (); + + // Ask underlying thread to stop. + void stop (); + + // Wait till undelying thread terminates. + void join (); + + // To be called when the whole infrastrucure is being closed (zs_term). + // It's vital to call the individual commands in this sequence: + // stop, join, shutdown. + void shutdown (); + + // Returns signaler associated with this I/O thread. + i_signaler *get_signaler (); + + // i_poll_events implementation. + void in_event (); + void out_event (); + void timer_event (); + + // i_thread implementation. + void attach_session (class session_t *session_); + void detach_session (class session_t *session_); + struct i_poller *get_poller (); + + // Command handlers. + void process_stop (); + + // Returns load experienced by the I/O thread. + int get_load (); + + private: + + // Clean-up. + ~io_thread_t (); + + // Poll thread gets notifications about incoming commands using + // this signaler. + fd_signaler_t signaler; + + // Handle associated with signaler's file descriptor. + handle_t signaler_handle; + + // I/O multiplexing is performed using a poller object. + i_poller *poller; + + // Vector of all sessions associated with this app thread. + typedef std::vector <class session_t*> sessions_t; + sessions_t sessions; + + }; + +} + +#endif diff --git a/src/ip.cpp b/src/ip.cpp new file mode 100644 index 0000000..f435bef --- /dev/null +++ b/src/ip.cpp @@ -0,0 +1,310 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> +#include <string.h> +#include <stdlib.h> +#include <string> + +#include "ip.hpp" +#include "platform.hpp" +#include "err.hpp" +#include "stdint.hpp" + +#if defined ZS_HAVE_SOLARIS + +#include <sys/sockio.h> +#include <net/if.h> +#include <unistd.h> + +// On Solaris platform, network interface name can be queried by ioctl. +static int resolve_nic_name (in_addr* addr_, char const *interface_) +{ + // * resolves to INADDR_ANY + if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) { + addr_->s_addr = htonl (INADDR_ANY); + return 0; + } + + // Create a socket. + int fd = socket (AF_INET, SOCK_DGRAM, 0); + zs_assert (fd != -1); + + // Retrieve number of interfaces. + lifnum ifn; + ifn.lifn_family = AF_UNSPEC; + ifn.lifn_flags = 0; + int rc = ioctl (fd, SIOCGLIFNUM, (char*) &ifn); + zs_assert (rc != -1); + + // Allocate memory to get interface names. + size_t ifr_size = sizeof (struct lifreq) * ifn.lifn_count; + char *ifr = (char*) malloc (ifr_size); + errno_assert (ifr); + + // Retrieve interface names. + lifconf ifc; + ifc.lifc_family = AF_UNSPEC; + ifc.lifc_flags = 0; + ifc.lifc_len = ifr_size; + ifc.lifc_buf = ifr; + rc = ioctl (fd, SIOCGLIFCONF, (char*) &ifc); + zs_assert (rc != -1); + + // Find the interface with the specified name and AF_INET family. + bool found = false; + lifreq *ifrp = ifc.lifc_req; + for (int n = 0; n < (int) (ifc.lifc_len / sizeof (lifreq)); + n ++, ifrp ++) { + if (!strcmp (interface_, ifrp->lifr_name)) { + rc = ioctl (fd, SIOCGLIFADDR, (char*) ifrp); + zs_assert (rc != -1); + if (ifrp->lifr_addr.ss_family == AF_INET) { + *addr_ = ((sockaddr_in*) &ifrp->lifr_addr)->sin_addr; + found = true; + break; + } + } + } + + // Clean-up. + free (ifr); + close (fd); + + // If interface was not found among interface names, we assume it's + // specified in the form of IP address. + if (!found) { + rc = inet_pton (AF_INET, interface_, addr_); + if (rc != 1) { + errno = EINVAL; + return -1; + } + } + + return 0; +} + +#elif defined ZS_HAVE_AIX || ZS_HAVE_HPUX + +#include <sys/types.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <net/if.h> + +#include "formatting.hpp" + +static int resolve_nic_name (in_addr* addr_, char const *interface_) +{ + // * resolves to INADDR_ANY + if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) { + addr_->s_addr = htonl (INADDR_ANY); + return 0; + } + + // Create a socket. + int sd = socket (AF_INET, SOCK_DGRAM, 0); + zs_assert (sd != -1); + + struct ifreq ifr; + + // Copy interface name for ioctl get. + zs_strncpy (ifr.ifr_name, interface_, sizeof (ifr.ifr_name)); + + // Fetch interface address. + int rc = ioctl (sd, SIOCGIFADDR, (caddr_t) &ifr, sizeof (struct ifreq)); + + if(rc != -1) { + struct sockaddr *sa = (struct sockaddr *) &ifr.ifr_addr; + *addr_ = ((sockaddr_in*)sa)->sin_addr; + } + else { + + // Assume interface_ is in IP format xxx.xxx.xxx.xxx. + rc = inet_pton (AF_INET, interface_, addr_); + if (rc != 0) { + errno = EINVAL; + return -1; + } + } + + // Clean up. + close (sd); + + return 0; +} + +#elif defined ZS_HAVE_WINDOWS + +static int resolve_nic_name (in_addr* addr_, char const *interface_) +{ + // * resolves to INADDR_ANY + if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) { + addr_->s_addr = htonl (INADDR_ANY); + return 0; + } + + // Windows doesn't use sensible NIC names. Thus, we expect IP address of + // the NIC instead. + in_addr addr; + ((sockaddr_in*) addr_)->sin_family = AF_INET; + addr.S_un.S_addr = inet_addr ((const char *) interface_); + if (addr.S_un.S_addr == INADDR_NONE) { + errno = EINVAL; + return -1; + } + *addr_ = addr; + + return 0; +} + +#elif ((defined ZS_HAVE_LINUX || defined ZS_HAVE_FREEBSD ||\ + defined ZS_HAVE_OSX || defined ZS_HAVE_OPENBSD ||\ + defined ZS_HAVE_QNXNTO) && defined ZS_HAVE_IFADDRS) + +#include <ifaddrs.h> + +// On these platforms, network interface name can be queried +// using getifaddrs function. +static int resolve_nic_name (in_addr* addr_, char const *interface_) +{ + // * resolves to INADDR_ANY + if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) { + addr_->s_addr = htonl (INADDR_ANY); + return 0; + } + + // Initialuse the output parameter. + memset (addr_, 0, sizeof (in_addr)); + + // Get the addresses. + ifaddrs* ifa = NULL; + int rc = getifaddrs (&ifa); + zs_assert (rc == 0); + zs_assert (ifa != NULL); + + // Find the corresponding network interface. + bool found = false; + for (ifaddrs *ifp = ifa; ifp != NULL ;ifp = ifp->ifa_next) + if (ifp->ifa_addr && ifp->ifa_addr->sa_family == AF_INET + && !strcmp (interface_, ifp->ifa_name)) + { + *addr_ = ((sockaddr_in*) ifp->ifa_addr)->sin_addr; + found = true; + } + + // Clean-up; + freeifaddrs (ifa); + + // If interface was not found among interface names, we assume it's + // specified in the form of IP address. + if (!found) { + rc = inet_pton (AF_INET, interface_, addr_); + if (rc != 1) { + errno = EINVAL; + return -1; + } + } + + return 0; +} + +#else + +// On other platforms interface name is interpreted as raw IP address. +static int resolve_nic_name (in_addr* addr_, char const *interface_) +{ + // * resolves to INADDR_ANY + if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) { + addr_->s_addr = htonl (INADDR_ANY); + return 0; + } + + // Convert IP address into sockaddr_in structure. + int rc = inet_pton (AF_INET, interface_, addr_); + zs_assert (rc != 0); + errno_assert (rc == 1); + + return 0; +} + +#endif + +int zs::resolve_ip_interface (sockaddr_in* addr_, char const *interface_) +{ + // Find the ':' that separates NIC name from port. + const char *delimiter = strchr (interface_, ':'); + if (!delimiter) { + errno = EINVAL; + return -1; + } + + // Clean the structure and fill in protocol family. + memset (addr_, 0, sizeof (sockaddr_in)); + addr_->sin_family = AF_INET; + + // Resolve the name of the NIC. + std::string nic_name (interface_, delimiter - interface_); + if (resolve_nic_name (&addr_->sin_addr, nic_name.c_str ()) != 0) + return -1; + + // Resolve the port. + addr_->sin_port = htons ((uint16_t) atoi (delimiter + 1)); + if (!addr_->sin_port) { + errno = EINVAL; + return 0; + } + + return 0; +} + +int zs::resolve_ip_hostname (sockaddr_in *addr_, const char *hostname_) +{ + // Find the ':' that separates hostname name from port. + const char *delimiter = strchr (hostname_, ':'); + if (!delimiter) { + errno = EINVAL; + return -1; + } + + // Separate the hostname. + std::string hostname (hostname_, delimiter - hostname_); + + // Resolve host name. + addrinfo req; + memset (&req, 0, sizeof (req)); + req.ai_family = AF_INET; + addrinfo *res; + int rc = getaddrinfo (hostname.c_str (), NULL, &req, &res); + if (rc) { + errno = EINVAL; + return -1; + } + zs_assert (res->ai_addr->sa_family == AF_INET); + memcpy (addr_, res->ai_addr, sizeof (sockaddr_in)); + freeaddrinfo (res); + + // Fill in the port number. + addr_->sin_port = htons ((uint16_t) atoi (delimiter + 1)); + if (!addr_->sin_port) { + errno = EINVAL; + return -1; + } + + return 0; +} diff --git a/src/ip.hpp b/src/ip.hpp new file mode 100644 index 0000000..90f02e3 --- /dev/null +++ b/src/ip.hpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_IP_HPP_INCLUDED__ +#define __ZS_IP_HPP_INCLUDED__ + +#include "platform.hpp" + +#ifdef ZS_HAVE_WINDOWS +#include "windows.hpp" +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netdb.h> +#endif + +namespace zs +{ + + // Resolves network interface name in <nic-name>:<port> format. Symbol "*" + // (asterisk) resolves to INADDR_ANY (all network interfaces). + int resolve_ip_interface (sockaddr_in* addr_, char const *interface_); + + // This function resolves a string in <hostname>:<port-number> format. + // Hostname can be either the name of the host or its IP address. + int resolve_ip_hostname (sockaddr_in *addr_, const char *hostname_); +} + +#endif diff --git a/src/kqueue.cpp b/src/kqueue.cpp new file mode 100644 index 0000000..b0c23ee --- /dev/null +++ b/src/kqueue.cpp @@ -0,0 +1,214 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "platform.hpp" + +#if defined ZS_HAVE_FREEBSD || defined ZS_HAVE_OPENBSD || defined ZS_HAVE_OSX + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/event.h> +#include <stdlib.h> +#include <unistd.h> +#include <algorithm> + +#include "kqueue.hpp" +#include "err.hpp" +#include "config.hpp" +#include "i_poll_events.hpp" + +zs::kqueue_t::kqueue_t () +{ + // Create event queue + kqueue_fd = kqueue (); + errno_assert (kqueue_fd != -1); +} + +zs::kqueue_t::~kqueue_t () +{ + close (kqueue_fd); +} + +void zs::kqueue_t::kevent_add (fd_t fd_, short filter_, void *udata_) +{ + struct kevent ev; + + EV_SET (&ev, fd_, filter_, EV_ADD, 0, 0, udata_); + int rc = kevent (kqueue_fd, &ev, 1, NULL, 0, NULL); + errno_assert (rc != -1); +} + +void zs::kqueue_t::kevent_delete (fd_t fd_, short filter_) +{ + struct kevent ev; + + EV_SET (&ev, fd_, filter_, EV_DELETE, 0, 0, NULL); + int rc = kevent (kqueue_fd, &ev, 1, NULL, 0, NULL); + errno_assert (rc != -1); +} + +zs::handle_t zs::kqueue_t::add_fd (fd_t fd_, i_poll_events *reactor_) +{ + poll_entry_t *pe = new poll_entry_t; + zs_assert (pe != NULL); + + pe->fd = fd_; + pe->flag_pollin = 0; + pe->flag_pollout = 0; + pe->reactor = reactor_; + + handle_t handle; + handle.ptr = pe; + return handle; +} + +void zs::kqueue_t::rm_fd (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + if (pe->flag_pollin) + kevent_delete (pe->fd, EVFILT_READ); + if (pe->flag_pollout) + kevent_delete (pe->fd, EVFILT_WRITE); + pe->fd = retired_fd; + retired.push_back (pe); +} + +void zs::kqueue_t::set_pollin (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->flag_pollin = true; + kevent_add (pe->fd, EVFILT_READ, pe); +} + +void zs::kqueue_t::reset_pollin (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->flag_pollin = false; + kevent_delete (pe->fd, EVFILT_READ); +} + +void zs::kqueue_t::set_pollout (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->flag_pollout = true; + kevent_add (pe->fd, EVFILT_WRITE, pe); +} + +void zs::kqueue_t::reset_pollout (handle_t handle_) +{ + poll_entry_t *pe = (poll_entry_t*) handle_.ptr; + pe->flag_pollout = false; + kevent_delete (pe->fd, EVFILT_WRITE); +} + +void zs::kqueue_t::add_timer (i_poll_events *events_) +{ + timers.push_back (events_); +} + +void zs::kqueue_t::cancel_timer (i_poll_events *events_) +{ + timers_t::iterator it = std::find (timers.begin (), timers.end (), events_); + if (it != timers.end ()) + timers.erase (it); +} + +int zs::kqueue_t::get_load () +{ + return load.get (); +} + +void zs::kqueue_t::start () +{ + worker.start (worker_routine, this); +} + +void zs::kqueue_t::stop () +{ + stopping = true; +} + +void zs::kqueue_t::join () +{ + worker.stop (); +} + +void zs::kqueue_t::loop () +{ + while (!stopping) { + + struct kevent ev_buf [max_io_events]; + + // Compute time interval to wait. + timespec timeout = {max_timer_period / 1000, + (max_timer_period % 1000) * 1000000}; + + // Wait for events. + int n = kevent (kqueue_fd, NULL, 0, + &ev_buf [0], max_io_events, timers.empty () ? NULL : &timeout); + if (n == -1 && errno == EINTR) + continue; + errno_assert (n != -1); + + // Handle timer. + if (!n) { + + // Use local list of timers as timer handlers may fill new timers + // into the original array. + timers_t t; + std::swap (timers, t); + + // Trigger all the timers. + for (timers_t::iterator it = t.begin (); it != t.end (); it ++) + (*it)->timer_event (); + + continue; + } + + for (int i = 0; i < n; i ++) { + poll_entry_t *pe = (poll_entry_t*) ev_buf [i].udata; + + if (pe->fd == retired_fd) + continue; + if (ev_buf [i].flags & EV_EOF) + pe->reactor->in_event (); + if (pe->fd == retired_fd) + continue; + if (ev_buf [i].filter == EVFILT_WRITE) + pe->reactor->out_event (); + if (pe->fd == retired_fd) + continue; + if (ev_buf [i].filter == EVFILT_READ) + pe->reactor->in_event (); + } + + // Destroy retired event sources. + for (retired_t::iterator it = retired.begin (); it != retired.end (); + it ++) + delete *it; + retired.clear (); + } +} + +void zs::kqueue_t::worker_routine (void *arg_) +{ + ((kqueue_t*) arg_)->loop (); +} + +#endif diff --git a/src/kqueue.hpp b/src/kqueue.hpp new file mode 100644 index 0000000..f060b28 --- /dev/null +++ b/src/kqueue.hpp @@ -0,0 +1,112 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_KQUEUE_HPP_INCLUDED__ +#define __ZS_KQUEUE_HPP_INCLUDED__ + +#include "platform.hpp" + +#if defined ZS_HAVE_FREEBSD || defined ZS_HAVE_OPENBSD || defined ZS_HAVE_OSX + +#include <vector> + +#include "i_poller.hpp" +#include "fd.hpp" +#include "thread.hpp" +#include "atomic_counter.hpp" + +namespace zs +{ + + // Implements socket polling mechanism using the BSD-specific + // kqueue interface. + + class kqueue_t : public i_poller + { + public: + + kqueue_t (); + virtual ~kqueue_t (); + + // i_poller implementation. + handle_t add_fd (fd_t fd_, i_poll_events *events_); + void rm_fd (handle_t handle_); + void set_pollin (handle_t handle_); + void reset_pollin (handle_t handle_); + void set_pollout (handle_t handle_); + void reset_pollout (handle_t handle_); + void add_timer (i_poll_events *events_); + void cancel_timer (i_poll_events *events_); + int get_load (); + void start (); + void stop (); + void join (); + + private: + + // Main worker thread routine. + static void worker_routine (void *arg_); + + // Main event loop. + void loop (); + + // File descriptor referring to the kernel event queue. + fd_t kqueue_fd; + + // Adds the event to the kqueue. + void kevent_add (fd_t fd_, short filter_, void *udata_); + + // Deletes the event from the kqueue. + void kevent_delete (fd_t fd_, short filter_); + + struct poll_entry_t + { + fd_t fd; + bool flag_pollin; + bool flag_pollout; + i_poll_events *reactor; + }; + + // List of retired event sources. + typedef std::vector <poll_entry_t*> retired_t; + retired_t retired; + + // List of all the engines waiting for the timer event. + typedef std::vector <struct i_poll_events*> timers_t; + timers_t timers; + + // If true, thread is in the process of shutting down. + bool stopping; + + // Handle of the physical thread doing the I/O work. + thread_t worker; + + // Load of the poller. Currently number of file descriptors + // registered with the poller. + atomic_counter_t load; + + kqueue_t (const kqueue_t&); + void operator = (const kqueue_t&); + }; + +} + +#endif + +#endif diff --git a/src/listener.cpp b/src/listener.cpp new file mode 100644 index 0000000..ae4a80f --- /dev/null +++ b/src/listener.cpp @@ -0,0 +1,170 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "listener.hpp" +#include "simple_semaphore.hpp" +#include "zmq_tcp_engine.hpp" +#include "io_thread.hpp" +#include "session_stub.hpp" +#include "session.hpp" +#include "err.hpp" +#include "dummy_aggregator.hpp" +#include "dummy_distributor.hpp" + +zs::listener_t::listener_t (io_thread_t *thread_, const char *addr_, + session_t *peer_, bool has_in_, bool has_out_, uint64_t taskset_) : + io_object_t (thread_), + poller (NULL), + addr (addr_), + peer (peer_), + taskset (taskset_), + has_in (has_in_), + has_out (has_out_) +{ +} + +void zs::listener_t::terminate () +{ + for (session_stubs_t::size_type i = 0; i != session_stubs.size (); i++) + session_stubs [i]->terminate (); + delete this; +} + +void zs::listener_t::shutdown () +{ + for (session_stubs_t::size_type i = 0; i != session_stubs.size (); i++) + session_stubs [i]->shutdown (); + delete this; +} + +zs::listener_t::~listener_t () +{ +} + +void zs::listener_t::got_identity (session_stub_t *session_stub_, + const char *identity_) +{ + // Get the engine allready disconnected from the stub and poller. + i_engine *engine = session_stub_->detach_engine (); + zs_assert (engine); + + // Find the corresponding session. + session_t *session; + sessions_t::iterator it = sessions.find (identity_); + + // Destroy the stub. + int i = session_stub_->get_index (); + session_stubs [i] = session_stubs [session_stubs.size () - 1]; + session_stubs [i]->set_index (i); + session_stubs.pop_back (); + session_stub_->terminate (); + + // If there's no session with the specified identity, create one. + if (it != sessions.end ()) { + session = it->second; + session->inc_seqnum (); + } + else { + + // Choose an I/O thread with the least load to handle the new session. + io_thread_t *io_thread = choose_io_thread (taskset); + + // Create the session and bind it to the I/O thread and peer. Make + // sure that the peer session won't get deallocated till it processes + // the subsequent bind command. + i_mux *mux = new dummy_aggregator_t; + zs_assert (mux); + i_demux *demux = new dummy_distributor_t; + zs_assert (demux); + session = new session_t (io_thread, io_thread, mux, demux, false, true); + zs_assert (session); + session->inc_seqnum (); + session->inc_seqnum (); + peer->inc_seqnum (); + send_reg_and_bind (session, peer, has_in, has_out); + } + + // Attach the engine to the session. + send_engine (session, engine); +} + +void zs::listener_t::process_reg (simple_semaphore_t *smph_) +{ + zs_assert (!poller); + poller = get_poller (); + + // Open the listening socket. + int rc = tcp_listener.open (addr.c_str ()); + zs_assert (rc == 0); + + // Unlock the application thread that created the listener. + if (smph_) + smph_->post (); + + // Start polling for incoming connections. + handle = poller->add_fd (tcp_listener.get_fd (), this); + poller->set_pollin (handle); +} + +void zs::listener_t::process_unreg (simple_semaphore_t *smph_) +{ + // Disassociate listener from the poller. + zs_assert (poller); + poller->rm_fd (handle); + poller = NULL; + + // Unlock the application thread closing the listener. + if (smph_) + smph_->post (); +} + +void zs::listener_t::in_event () +{ + fd_t fd = tcp_listener.accept (); + + // If connection was reset by the peer in the meantime, just ignore it. + // TODO: Handle specific errors like ENFILE/EMFILE etc. + if (fd == retired_fd) + return; + + // Create an session stub for the engine to take care for it till its + // identity is retreived. + session_stub_t *session_stub = new session_stub_t (this); + zs_assert (session_stub); + session_stub->set_index (session_stubs.size ()); + session_stubs.push_back (session_stub); + + // Create an engine to encaspulate the socket. Engine will register itself + // with the stub so the stub will be able to free it in case of shutdown. + zmq_tcp_engine_t *engine = new zmq_tcp_engine_t (fd); + zs_assert (engine); + engine->attach (poller, session_stub); +} + +void zs::listener_t::out_event () +{ + zs_assert (false); +} + +void zs::listener_t::timer_event () +{ + zs_assert (false); +} + + diff --git a/src/listener.hpp b/src/listener.hpp new file mode 100644 index 0000000..f3c745a --- /dev/null +++ b/src/listener.hpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_LISTENER_HPP_INCLUDED__ +#define __ZS_LISTENER_HPP_INCLUDED__ + +#include <map> +#include <vector> +#include <string> + +#include "io_object.hpp" +#include "tcp_listener.hpp" +#include "i_poller.hpp" +#include "i_poll_events.hpp" +#include "stdint.hpp" + +namespace zs +{ + + class listener_t : public io_object_t, public i_poll_events + { + public: + + listener_t (class io_thread_t *thread_, const char *addr_, + class session_t *peer_, bool has_in_, bool has_out_, + uint64_t taskset_); + + void terminate (); + void shutdown (); + + // This function is called by session stub once the identity + // is retrieved from the incoming connection. + void got_identity (class session_stub_t *session_stub_, + const char *identity_); + + void process_reg (class simple_semaphore_t *smph_); + void process_unreg (class simple_semaphore_t *smph_); + + // i_poll_events implementation. + void in_event (); + void out_event (); + void timer_event (); + + private: + + ~listener_t (); + + struct i_poller *poller; + + // Handle corresponding to the listening socket. + handle_t handle; + + // Actual listening socket. + tcp_listener_t tcp_listener; + + // Address to bind to. + std::string addr; + + // Peer session. All the newly created connections should bind to + // this session. + session_t *peer; + + // Taskset specifies which I/O threads are to be use to handle + // newly created connections (0 = all). + uint64_t taskset; + + // Sessions created by this listener are stored in this map. They are + // indexed by peer identities so that the same peer connects to the + // same session after reconnection. + // NB: Sessions are destroyed from other place and possibly later on, + // so no need to care about them during listener object termination. + typedef std::map <std::string, class session_t*> sessions_t; + sessions_t sessions; + + // List of engines (bound to temorary session stubs) that we haven't + // retrieved the identity from so far. + typedef std::vector <class session_stub_t*> session_stubs_t; + session_stubs_t session_stubs; + + // If true, create inbound pipe when binding new connection + // to the peer. + bool has_in; + + // If true, create outbound pipe when binding new connection + // to the peer. + bool has_out; + + listener_t (const listener_t&); + void operator = (const listener_t&); + }; + +} + +#endif diff --git a/src/load_balancer.cpp b/src/load_balancer.cpp new file mode 100644 index 0000000..63dc15c --- /dev/null +++ b/src/load_balancer.cpp @@ -0,0 +1,130 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "load_balancer.hpp" +#include "pipe_writer.hpp" +#include "err.hpp" +#include "session.hpp" +#include "msg.hpp" + +zs::load_balancer_t::load_balancer_t () : + session (NULL), + current (0) +{ +} + +void zs::load_balancer_t::set_session (session_t *session_) +{ + zs_assert (!session); + session = session_; +} + +void zs::load_balancer_t::shutdown () +{ + // No need to deallocate pipes here. They'll be deallocated during the + // shutdown of the dispatcher. + delete this; +} + +void zs::load_balancer_t::terminate () +{ + // Pipe unregisters itself during the call to terminate, so the pipes + // list shinks by one in each iteration. + while (!pipes.empty ()) + pipes [0]->terminate (); + + delete this; +} + +zs::load_balancer_t::~load_balancer_t () +{ +} + +void zs::load_balancer_t::attach_pipe (pipe_writer_t *pipe_) +{ + // Associate demux with a new pipe. + pipe_->set_demux (this); + pipe_->set_index (pipes.size ()); + pipes.push_back (pipe_); +} + +void zs::load_balancer_t::detach_pipe (pipe_writer_t *pipe_) +{ + // Release the reference to the pipe. + int index = pipe_->get_index (); + pipe_->set_index (-1); + pipes [index] = pipes.back (); + pipes [index]->set_index (index); + pipes.pop_back (); +} + +bool zs::load_balancer_t::empty () +{ + return pipes.empty (); +} + +bool zs::load_balancer_t::send (zs_msg *msg_) +{ + // If there are no pipes, message cannot be sent. + if (pipes.size () == 0) + return false; + + // Find the first pipe that is ready to accept the message. + bool found = false; + for (pipes_t::size_type i = 0; !found && i < pipes.size (); i++) { +// if (pipes [current]->check_write (msg)) + found = true; +// else +// current = (current + 1) % pipes.size (); + } + + // Oops, no pipe is ready to accept the message. + if (!found) + return false; + + // Send the message to the selected pipe. + write_to_pipe (pipes [current], msg_); + current = (current + 1) % pipes.size (); + + // Detach the original message from the data buffer. + zs_msg_init (msg_); + + return true; +} + +void zs::load_balancer_t::flush () +{ + // Flush all pipes. If there's large number of pipes, it can be pretty + // inefficient (especially if there's new message only in a single pipe). + // Can it be improved? + for (pipes_t::iterator it = pipes.begin (); it != pipes.end (); it ++) + (*it)->flush (); +} + +void zs::load_balancer_t::write_to_pipe (class pipe_writer_t *pipe_, + struct zs_msg *msg_) +{ + if (!pipe_->write (msg_)) { + // TODO: Push gap notification to the pipe. + zs_assert (false); + } +} + diff --git a/src/load_balancer.hpp b/src/load_balancer.hpp new file mode 100644 index 0000000..9cdc68f --- /dev/null +++ b/src/load_balancer.hpp @@ -0,0 +1,73 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_LOAD_BALANCER_HPP_INCLUDED__ +#define __ZS_LOAD_BALANCER_HPP_INCLUDED__ + +#include <vector> + +#include <i_demux.hpp> + +namespace zs +{ + + // Object to distribute messages to outbound pipes. + + class load_balancer_t : public i_demux + { + public: + + load_balancer_t (); + + // i_demux implementation. + void set_session (class session_t *session_); + void shutdown (); + void terminate (); + void attach_pipe (class pipe_writer_t *pipe_); + void detach_pipe (class pipe_writer_t *pipe_); + bool empty (); + bool send (struct zs_msg *msg_); + void flush (); + + private: + + // Clean-up. + ~load_balancer_t (); + + // Reference to the owner session object. + class session_t *session; + + // Writes the message to the pipe if possible. If it isn't, writes + // a gap notification to the pipe. + void write_to_pipe (class pipe_writer_t *pipe_, struct zs_msg *msg_); + + // The list of outbound pipes. + typedef std::vector <class pipe_writer_t*> pipes_t; + pipes_t pipes; + + // Current pipe to write next message to. + pipes_t::size_type current; + + load_balancer_t (const load_balancer_t&); + void operator = (const load_balancer_t&); + }; + +} + +#endif diff --git a/src/msg.hpp b/src/msg.hpp new file mode 100644 index 0000000..f4f4d26 --- /dev/null +++ b/src/msg.hpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_MSG_HPP_INCLUDE__ +#define __ZS_MSG_HPP_INCLUDE__ + +#include <stddef.h> + +#include "../include/zs.h" + +#include "atomic_counter.hpp" + +//namespace zs +//{ + + // Shared message buffer. Message data are either allocated in one + // continguous block along with this structure - thus avoiding one + // malloc/free pair or they are stored in used-supplied memory. + // In the latter case, ffn member stores pointer to the function to be + // used to deallocate the data. If the buffer is actually shared (there + // are at least 2 references to it) refcount member contains number of + // references. + struct zs_msg_content + { + void *data; + size_t size; + zs_free_fn *ffn; + zs::atomic_counter_t refcnt; + }; + +//} + +#endif diff --git a/src/mutex.hpp b/src/mutex.hpp new file mode 100644 index 0000000..a7f95da --- /dev/null +++ b/src/mutex.hpp @@ -0,0 +1,116 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_MUTEX_HPP_INCLUDED__ +#define __ZS_MUTEX_HPP_INCLUDED__ + +#include "platform.hpp" +#include "err.hpp" + +// Mutex class encapsulates OS mutex in a platform-independent way. + +#ifdef ZS_HAVE_WINDOWS + +#include "windows.hpp" + +namespace zs +{ + + class mutex_t + { + public: + inline mutex_t () + { + InitializeCriticalSection (&cs); + } + + inline ~mutex_t () + { + DeleteCriticalSection (&cs); + } + + inline void lock () + { + EnterCriticalSection (&cs); + } + + inline void unlock () + { + LeaveCriticalSection (&cs); + } + + private: + + CRITICAL_SECTION cs; + + // Disable copy construction and assignment. + mutex_t (const mutex_t&); + void operator = (const mutex_t&); + }; + +} + +#else + +#include <pthread.h> + +namespace zs +{ + + class mutex_t + { + public: + inline mutex_t () + { + int rc = pthread_mutex_init (&mutex, NULL); + errno_assert (rc == 0); + } + + inline ~mutex_t () + { + int rc = pthread_mutex_destroy (&mutex); + errno_assert (rc == 0); + } + + inline void lock () + { + int rc = pthread_mutex_lock (&mutex); + errno_assert (rc == 0); + } + + inline void unlock () + { + int rc = pthread_mutex_unlock (&mutex); + errno_assert (rc == 0); + } + + private: + + pthread_mutex_t mutex; + + // Disable copy construction and assignment. + mutex_t (const mutex_t&); + void operator = (const mutex_t&); + }; + +} + +#endif + +#endif diff --git a/src/object.cpp b/src/object.cpp new file mode 100644 index 0000000..8a154ae --- /dev/null +++ b/src/object.cpp @@ -0,0 +1,294 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "object.hpp" +#include "dispatcher.hpp" +#include "err.hpp" +#include "pipe_reader.hpp" +#include "pipe_writer.hpp" +#include "session.hpp" +#include "io_thread.hpp" +#include "simple_semaphore.hpp" +#include "i_engine.hpp" + +zs::object_t::object_t (dispatcher_t *dispatcher_, int thread_slot_) : + dispatcher (dispatcher_), + thread_slot (thread_slot_) +{ +} + +zs::object_t::object_t (object_t *parent_) : + dispatcher (parent_->dispatcher), + thread_slot (parent_->thread_slot) +{ +} + +zs::object_t::~object_t () +{ +} + +int zs::object_t::thread_slot_count () +{ + return dispatcher->thread_slot_count (); +} + +int zs::object_t::get_thread_slot () +{ + return thread_slot; +} + +void zs::object_t::process_command (command_t &cmd_) +{ + switch (cmd_.type) { + + case command_t::head: + process_head (cmd_.args.head.bytes); + break; + + case command_t::tail: + process_tail (cmd_.args.tail.bytes); + break; + + case command_t::engine: + process_engine (cmd_.args.engine.engine); + break; + + case command_t::bind: + process_bind (cmd_.args.bind.reader, cmd_.args.bind.peer); + break; + + case command_t::reg: + process_reg (cmd_.args.reg.smph); + break; + + case command_t::reg_and_bind: + process_reg_and_bind (cmd_.args.reg_and_bind.peer, + cmd_.args.reg_and_bind.flow_in, cmd_.args.reg_and_bind.flow_out); + break; + + case command_t::unreg: + process_unreg (cmd_.args.unreg.smph); + break; + + case command_t::terminate: + process_terminate (); + break; + + case command_t::terminate_ack: + process_terminate_ack (); + break; + + case command_t::stop: + process_stop (); + return; + + default: + zs_assert (false); + } +} + +void zs::object_t::create_pipe (object_t *reader_parent_, + object_t *writer_parent_, uint64_t hwm_, uint64_t lwm_, + pipe_reader_t **reader_, pipe_writer_t **writer_) +{ + dispatcher->create_pipe (reader_parent_, writer_parent_, hwm_, lwm_, + reader_, writer_); +} + +void zs::object_t::destroy_pipe (pipe_t *pipe_) +{ + dispatcher->destroy_pipe (pipe_); +} + +int zs::object_t::register_inproc_endpoint (const char *endpoint_, + session_t *session_) +{ + return dispatcher->register_inproc_endpoint (endpoint_, session_); +} + +zs::object_t *zs::object_t::get_inproc_endpoint (const char *endpoint_) +{ + return dispatcher->get_inproc_endpoint (endpoint_); +} + +void zs::object_t::unregister_inproc_endpoints (session_t *session_) +{ + dispatcher->unregister_inproc_endpoints (session_); +} + +zs::io_thread_t *zs::object_t::choose_io_thread (uint64_t taskset_) +{ + return dispatcher->choose_io_thread (taskset_); +} + +void zs::object_t::send_stop () +{ + // Send command goes always to the current object. To-self pipe is + // used exclusively for sending this command. + command_t cmd; + cmd.destination = this; + cmd.type = command_t::stop; + dispatcher->write (thread_slot, thread_slot, cmd); +} + +void zs::object_t::send_bind (object_t *destination_, pipe_reader_t *reader_, + session_t *peer_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::bind; + cmd.args.bind.reader = reader_; + cmd.args.bind.peer = peer_; + send_command (cmd); +} + +void zs::object_t::send_head (object_t *destination_, uint64_t bytes_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::head; + cmd.args.head.bytes = bytes_; + send_command (cmd); +} + +void zs::object_t::send_tail (object_t *destination_, uint64_t bytes_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::tail; + cmd.args.tail.bytes = bytes_; + send_command (cmd); +} + +void zs::object_t::send_reg (object_t *destination_, simple_semaphore_t *smph_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::reg; + cmd.args.reg.smph = smph_; + send_command (cmd); +} + +void zs::object_t::send_reg_and_bind (object_t *destination_, + session_t *peer_, bool flow_in_, bool flow_out_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::reg_and_bind; + cmd.args.reg_and_bind.peer = peer_; + cmd.args.reg_and_bind.flow_in = flow_in_; + cmd.args.reg_and_bind.flow_out = flow_out_; + send_command (cmd); +} + +void zs::object_t::send_unreg (object_t *destination_, + simple_semaphore_t *smph_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::unreg; + cmd.args.unreg.smph = smph_; + send_command (cmd); +} + +void zs::object_t::send_engine (object_t *destination_, i_engine *engine_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::engine; + cmd.args.engine.engine = engine_; + send_command (cmd); +} + +void zs::object_t::send_terminate (object_t *destination_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::terminate; + send_command (cmd); +} + +void zs::object_t::send_terminate_ack (object_t *destination_) +{ + command_t cmd; + cmd.destination = destination_; + cmd.type = command_t::terminate_ack; + send_command (cmd); +} + +void zs::object_t::process_stop () +{ + zs_assert (false); +} + +void zs::object_t::process_bind (pipe_reader_t *reader_, session_t *peer_) +{ + zs_assert (false); +} + +void zs::object_t::process_head (uint64_t bytes_) +{ + zs_assert (false); +} + +void zs::object_t::process_tail (uint64_t bytes_) +{ + zs_assert (false); +} + +void zs::object_t::process_reg (simple_semaphore_t *smph_) +{ + zs_assert (false); +} + +void zs::object_t::process_reg_and_bind (session_t *session_, + bool flow_in_, bool flow_out_) +{ + zs_assert (false); +} + +void zs::object_t::process_unreg (simple_semaphore_t *smph_) +{ + zs_assert (false); +} + +void zs::object_t::process_engine (i_engine *engine_) +{ + zs_assert (false); +} + +void zs::object_t::process_terminate () +{ + zs_assert (false); +} + +void zs::object_t::process_terminate_ack () +{ + zs_assert (false); +} + +void zs::object_t::send_command (command_t &cmd_) +{ + int destination_thread_slot = cmd_.destination->get_thread_slot (); + if (destination_thread_slot == thread_slot) + cmd_.destination->process_command (cmd_); + else + dispatcher->write (thread_slot, destination_thread_slot, cmd_); +} + diff --git a/src/object.hpp b/src/object.hpp new file mode 100644 index 0000000..a4f93ae --- /dev/null +++ b/src/object.hpp @@ -0,0 +1,105 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_OBJECT_HPP_INCLUDED__ +#define __ZS_OBJECT_HPP_INCLUDED__ + +#include "stdint.hpp" + +namespace zs +{ + + // Base class for all objects that participate in inter-thread + // communication. + + class object_t + { + public: + + object_t (class dispatcher_t *dispatcher_, int thread_slot_); + object_t (object_t *parent_); + ~object_t (); + + int get_thread_slot (); + void process_command (struct command_t &cmd_); + + protected: + + // Derived object can use following functions to interact with + // global repositories. See dispatcher.hpp for function details. + int thread_slot_count (); + void create_pipe (class object_t *reader_parent_, + class object_t *writer_parent_, uint64_t hwm_, uint64_t lwm_, + class pipe_reader_t **reader_, class pipe_writer_t **writer_); + void destroy_pipe (class pipe_t *pipe_); + int register_inproc_endpoint (const char *endpoint_, + class session_t *session_); + class object_t *get_inproc_endpoint (const char *endpoint_); + void unregister_inproc_endpoints (class session_t *session_); + class io_thread_t *choose_io_thread (uint64_t taskset_); + + // Derived object can use these functions to send commands + // to other objects. + void send_stop (); + void send_bind (object_t *destination_, class pipe_reader_t *reader_, + class session_t *peer_); + void send_head (object_t *destination_, uint64_t bytes_); + void send_tail (object_t *destination_, uint64_t bytes_); + void send_reg (object_t *destination_, + class simple_semaphore_t *smph_); + void send_reg_and_bind (object_t *destination_, class session_t *peer_, + bool flow_in_, bool flow_out_); + void send_unreg (object_t *destination_, + class simple_semaphore_t *smph_); + void send_engine (object_t *destination_, struct i_engine *engine_); + void send_terminate (object_t *destination_); + void send_terminate_ack (object_t *destination_); + + // These handlers can be overloaded by the derived objects. They are + // called when command arrives from another thread. + virtual void process_stop (); + virtual void process_bind (class pipe_reader_t *reader_, + class session_t *peer_); + virtual void process_head (uint64_t bytes_); + virtual void process_tail (uint64_t bytes_); + virtual void process_reg (class simple_semaphore_t *smph_); + virtual void process_reg_and_bind (class session_t *peer_, + bool flow_in_, bool flow_out_); + virtual void process_unreg (class simple_semaphore_t *smph_); + virtual void process_engine (struct i_engine *engine_); + virtual void process_terminate (); + virtual void process_terminate_ack (); + + // Pointer to the root of the infrastructure. + class dispatcher_t *dispatcher; + + // Slot ID of the thread the object belongs to. + int thread_slot; + + private: + + void send_command (command_t &cmd_); + + object_t (const object_t&); + void operator = (const object_t&); + }; + +} + +#endif diff --git a/src/p2p.cpp b/src/p2p.cpp new file mode 100644 index 0000000..f15b663 --- /dev/null +++ b/src/p2p.cpp @@ -0,0 +1,29 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "p2p.hpp" +#include "app_thread.hpp" +#include "session.hpp" + +zs::p2p_t::p2p_t (app_thread_t *thread_, session_t *session_) : + socket_base_t (thread_, session_) +{ +} diff --git a/src/p2p.hpp b/src/p2p.hpp new file mode 100644 index 0000000..1032a61 --- /dev/null +++ b/src/p2p.hpp @@ -0,0 +1,42 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_P2P_HPP_INCLUDED__ +#define __ZS_P2P_HPP_INCLUDED__ + +#include "socket_base.hpp" + +namespace zs +{ + + class p2p_t : public socket_base_t + { + public: + + p2p_t (class app_thread_t *thread_, class session_t *session_); + + private: + + p2p_t (const p2p_t&); + void operator = (const p2p_t&); + }; + +} + +#endif diff --git a/src/pipe.cpp b/src/pipe.cpp new file mode 100644 index 0000000..26042ae --- /dev/null +++ b/src/pipe.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pipe.hpp" + +zs::pipe_t::pipe_t () : + ypipe_t <zs_msg, false, message_pipe_granularity> (false), + index (-1) +{ +} + +zs::pipe_t::~pipe_t () +{ + // Flush any outstanding messages to the pipe. + flush (); + + // Deallocate all the messages in the pipe. + zs_msg msg; + while (read (&msg)) + zs_msg_close (&msg); +} + +void zs::pipe_t::set_index (int index_) +{ + index = index_; +} + +int zs::pipe_t::get_index () +{ + return index; +} diff --git a/src/pipe.hpp b/src/pipe.hpp new file mode 100644 index 0000000..c0e722d --- /dev/null +++ b/src/pipe.hpp @@ -0,0 +1,57 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_PIPE_HPP_INCLUDED__ +#define __ZS_PIPE_HPP_INCLUDED__ + +#include "../include/zs.h" + +#include "ypipe.hpp" +#include "config.hpp" + +namespace zs +{ + + // Message pipe. A simple wrapper on top of ypipe. + + class pipe_t : public ypipe_t <zs_msg, false, message_pipe_granularity> + { + // Dispatcher is a friend so that it can create & destroy the pipes. + // By making constructor & destructor private we are sure that nobody + // except dispatcher messes with pipes. + friend class dispatcher_t; + + private: + + pipe_t (); + ~pipe_t (); + + void set_index (int index_); + int get_index (); + + // Index of the pipe in dispatcher's array of pipes. + int index; + + pipe_t (const pipe_t&); + void operator = (const pipe_t&); + }; + +} + +#endif diff --git a/src/pipe_reader.cpp b/src/pipe_reader.cpp new file mode 100644 index 0000000..5585b92 --- /dev/null +++ b/src/pipe_reader.cpp @@ -0,0 +1,118 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "pipe_reader.hpp" +#include "pipe.hpp" +#include "err.hpp" +#include "i_mux.hpp" + +zs::pipe_reader_t::pipe_reader_t (object_t *parent_, pipe_t *pipe_, + uint64_t hwm_, uint64_t lwm_) : + object_t (parent_), + pipe (pipe_), + peer (NULL), + mux (NULL), + index (-1), + hwm (hwm_), + lwm (lwm_), + head (0), + tail (0), + last_sent_head (0) +{ +} + +void zs::pipe_reader_t::set_peer (object_t *peer_) +{ + peer = peer_; +} + +zs::pipe_reader_t::~pipe_reader_t () +{ +} + +void zs::pipe_reader_t::set_mux (i_mux *mux_) +{ + mux = mux_; +} + +void zs::pipe_reader_t::set_index (int index_) +{ + index = index_; +} + +int zs::pipe_reader_t::get_index () +{ + return index; +} + +void zs::pipe_reader_t::process_tail (uint64_t bytes_) +{ + tail = bytes_; + mux->reactivate (this); +} + +bool zs::pipe_reader_t::read (struct zs_msg *msg_) +{ + // Read a message. + if (!pipe->read (msg_)) { + mux->deactivate (this); + return false; + } + + // If successfull, adjust the head of the pipe. + head += zs_msg_size (msg_); + + // If pipe writer wasn't notified about the head position for long enough, + // notify it. + if (head - last_sent_head >= hwm - lwm) { + send_head (peer, head); + last_sent_head = head; + } + + if (zs_msg_type (msg_) == ZS_DELIMITER) { + + // Detach the pipe from the mux and send termination request to + // the pipe writer. + mux->detach_pipe (this); + mux = NULL; + send_terminate (peer); + return false; + } + + return true; +} + +void zs::pipe_reader_t::terminate () +{ + // Detach the pipe from the mux and send termination request to + // the pipe writer. + if (mux) { + mux->detach_pipe (this); + mux = NULL; + } + send_terminate (peer); +} + +void zs::pipe_reader_t::process_terminate_ack () +{ + // Ask dispatcher to deallocate the pipe. + destroy_pipe (pipe); +} diff --git a/src/pipe_reader.hpp b/src/pipe_reader.hpp new file mode 100644 index 0000000..bc3fd2e --- /dev/null +++ b/src/pipe_reader.hpp @@ -0,0 +1,89 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_PIPE_READER_HPP_INCLUDED__ +#define __ZS_PIPE_READER_HPP_INCLUDED__ + +#include "object.hpp" +#include "stdint.hpp" + +namespace zs +{ + + class pipe_reader_t : public object_t + { + // Dispatcher is a friend so that it can create & destroy the reader. + // By making constructor & destructor private we are sure that nobody + // except dispatcher messes with readers. + friend class dispatcher_t; + + public: + + // Set & get index in the associated mux object. + void set_mux (struct i_mux *mux_); + void set_index (int index_); + int get_index (); + + // Reads a message to the underlying pipe. + bool read (struct zs_msg *msg_); + + // Asks pipe to destroy itself. + void terminate (); + + private: + + pipe_reader_t (class object_t *parent_, class pipe_t *pipe_, + uint64_t hwm_, uint64_t lwm_); + ~pipe_reader_t (); + + // Second step of reader construction. The parameter cannot be passed + // in constructor as peer object doesn't yet exist at the time. + void set_peer (class object_t *peer_); + + void process_tail (uint64_t bytes_); + void process_terminate_ack (); + + // The underlying pipe. + class pipe_t *pipe; + + // Pipe writer associated with the other side of the pipe. + class object_t *peer; + + // Associated mux object. + struct i_mux *mux; + + // Index in the associated mux object. + int index; + + // High and low watermarks for in-memory storage (in bytes). + uint64_t hwm; + uint64_t lwm; + + // Positions of head and tail of the pipe (in bytes). + uint64_t head; + uint64_t tail; + uint64_t last_sent_head; + + pipe_reader_t (const pipe_reader_t&); + void operator = (const pipe_reader_t&); + }; + +} + +#endif diff --git a/src/pipe_writer.cpp b/src/pipe_writer.cpp new file mode 100644 index 0000000..173cf97 --- /dev/null +++ b/src/pipe_writer.cpp @@ -0,0 +1,120 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "pipe_writer.hpp" +#include "pipe.hpp" +#include "i_demux.hpp" + +zs::pipe_writer_t::pipe_writer_t (object_t *parent_, pipe_t *pipe_, + object_t *peer_, uint64_t hwm_, uint64_t lwm_) : + object_t (parent_), + pipe (pipe_), + peer (peer_), + demux (NULL), + index (-1), + hwm (hwm_), + lwm (lwm_), + head (0), + tail (0) +{ +} + +zs::pipe_writer_t::~pipe_writer_t () +{ +} + +void zs::pipe_writer_t::set_demux (i_demux *demux_) +{ + demux = demux_; +} + +void zs::pipe_writer_t::set_index (int index_) +{ + index = index_; +} + +int zs::pipe_writer_t::get_index () +{ + return index; +} + +bool zs::pipe_writer_t::write (zs_msg *msg_) +{ + size_t msg_size = zs_msg_size (msg_); + + // If message won't fit into the in-memory pipe, there's no way + // to pass it further. + // TODO: It should be discarded and 'oversized' notification should be + // placed into the pipe. + zs_assert (!hwm || msg_size <= hwm); + + // If there's not enough space in the pipe at the moment, return false. + if (hwm && tail + msg_size - head > hwm) + return false; + + // Write the message to the pipe and adjust tail position. + pipe->write (*msg_); + flush (); + tail += msg_size; + + return true; +} + +void zs::pipe_writer_t::flush () +{ + if (!pipe->flush ()) + send_tail (peer, tail); +} + +void zs::pipe_writer_t::process_head (uint64_t bytes_) +{ + head = bytes_; +} + +void zs::pipe_writer_t::terminate () +{ + // Disconnect from the associated demux. + if (demux) { + demux->detach_pipe (this); + demux = NULL; + } + + // Push the delimiter to the pipe. Delimiter is a notification for pipe + // reader that there will be no more messages in the pipe. + zs_msg delimiter; + delimiter.content = (zs_msg_content*) ZS_DELIMITER; + delimiter.shared = false; + delimiter.vsm_size = 0; + pipe->write (delimiter); + flush (); +} + +void zs::pipe_writer_t::process_terminate () +{ + // Disconnect from the associated demux. + if (demux) { + demux->detach_pipe (this); + demux = NULL; + } + + // Send termination acknowledgement to the pipe reader. + send_terminate_ack (peer); +} diff --git a/src/pipe_writer.hpp b/src/pipe_writer.hpp new file mode 100644 index 0000000..3b4b4cd --- /dev/null +++ b/src/pipe_writer.hpp @@ -0,0 +1,88 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_PIPE_WRITER_HPP_INCLUDED__ +#define __ZS_PIPE_WRITER_HPP_INCLUDED__ + +#include "object.hpp" +#include "stdint.hpp" + +namespace zs +{ + + class pipe_writer_t : public object_t + { + // Dispatcher is a friend so that it can create & destroy the writer. + // By making constructor & destructor private we are sure that nobody + // except dispatcher messes with writers. + friend class dispatcher_t; + + public: + + // Set & get index in the associated demux object. + void set_demux (struct i_demux *demux_); + void set_index (int index_); + int get_index (); + + // Writes a message to the underlying pipe. Returns false if the + // message cannot be written to the pipe at the moment. + bool write (struct zs_msg *msg_); + + // Flush the messages downsteam. + void flush (); + + // Asks pipe to destroy itself. + void terminate (); + + private: + + pipe_writer_t (class object_t *parent_, class pipe_t *pipe_, + class object_t *peer_, uint64_t hwm_, uint64_t lwm_); + ~pipe_writer_t (); + + void process_head (uint64_t bytes_); + void process_terminate (); + + // The underlying pipe. + class pipe_t *pipe; + + // Pipe reader associated with the other side of the pipe. + class object_t *peer; + + // Associated demux object. + struct i_demux *demux; + + // Index in the associated demux object. + int index; + + // High and low watermarks for in-memory storage (in bytes). + uint64_t hwm; + uint64_t lwm; + + // Positions of head and tail of the pipe (in bytes). + uint64_t head; + uint64_t tail; + + pipe_writer_t (const pipe_writer_t&); + void operator = (const pipe_writer_t&); + }; + +} + +#endif diff --git a/src/platform.hpp.in b/src/platform.hpp.in new file mode 100644 index 0000000..108d50d --- /dev/null +++ b/src/platform.hpp.in @@ -0,0 +1,210 @@ +/* src/platform.hpp.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the <arpa/inet.h> header file. */ +#undef HAVE_ARPA_INET_H + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the <errno.h> header file. */ +#undef HAVE_ERRNO_H + +/* Define to 1 if you have the `freeifaddrs' function. */ +#undef HAVE_FREEIFADDRS + +/* Define to 1 if you have the `getifaddrs' function. */ +#undef HAVE_GETIFADDRS + +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define to 1 if you have the <ifaddrs.h> header file. */ +#undef HAVE_IFADDRS_H + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#undef HAVE_LIBPTHREAD + +/* Define to 1 if you have the `rt' library (-lrt). */ +#undef HAVE_LIBRT + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define to 1 if you have the `uuid' library (-luuid). */ +#undef HAVE_LIBUUID + +/* Define to 1 if you have the <limits.h> header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#undef HAVE_MALLOC + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#undef HAVE_MEMSET + +/* Define to 1 if you have the <netinet/in.h> header file. */ +#undef HAVE_NETINET_IN_H + +/* Define to 1 if you have the <netinet/tcp.h> header file. */ +#undef HAVE_NETINET_TCP_H + +/* Define to 1 if you have the `perror' function. */ +#undef HAVE_PERROR + +/* Define to 1 if you have the `socket' function. */ +#undef HAVE_SOCKET + +/* Define to 1 if stdbool.h conforms to C99. */ +#undef HAVE_STDBOOL_H + +/* Define to 1 if you have the <stddef.h> header file. */ +#undef HAVE_STDDEF_H + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the <sys/eventfd.h> header file. */ +#undef HAVE_SYS_EVENTFD_H + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/time.h> header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the <windows.h> header file. */ +#undef HAVE_WINDOWS_H + +/* Define to 1 if the system has the type `_Bool'. */ +#undef HAVE__BOOL + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +#undef NO_MINUS_C_MINUS_O + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define as the return type of signal handlers (`int' or `void'). */ +#undef RETSIGTYPE + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */ +#undef TIME_WITH_SYS_TIME + +/* Version number of package */ +#undef VERSION + +/* Force to use mutexes */ +#undef ZS_FORCE_MUTEXES + +/* Have AIX OS */ +#undef ZS_HAVE_AIX + +/* Have eventfd extension. */ +#undef ZS_HAVE_EVENTFD + +/* Have FreeBSD OS */ +#undef ZS_HAVE_FREEBSD + +/* Have HPUX OS */ +#undef ZS_HAVE_HPUX + +/* Have ifaddrs.h header. */ +#undef ZS_HAVE_IFADDRS + +/* Have Linux OS */ +#undef ZS_HAVE_LINUX + +/* Have MinGW32 */ +#undef ZS_HAVE_MINGW32 + +/* Have OpenBSD OS */ +#undef ZS_HAVE_OPENBSD + +/* Have DarwinOSX OS */ +#undef ZS_HAVE_OSX + +/* Have QNX Neutrino OS */ +#undef ZS_HAVE_QNXNTO + +/* Have Solaris OS */ +#undef ZS_HAVE_SOLARIS + +/* Have Windows OS */ +#undef ZS_HAVE_WINDOWS + +/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>, + <pthread.h>, or <semaphore.h> is not used. If the typedef was allowed, the + #define below would cause a syntax error. */ +#undef _UINT32_T + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to rpl_malloc if the replacement function should be used. */ +#undef malloc + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +#undef size_t + +/* Define to `int' if <sys/types.h> does not define. */ +#undef ssize_t + +/* Define to the type of an unsigned integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#undef uint32_t + +/* Define to empty if the keyword `volatile' does not work. Warning: valid + code using `volatile' can become incorrect without. Disable with care. */ +#undef volatile diff --git a/src/poll.cpp b/src/poll.cpp new file mode 100644 index 0000000..59a0cd7 --- /dev/null +++ b/src/poll.cpp @@ -0,0 +1,205 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "platform.hpp" + +#if defined ZS_HAVE_LINUX || defined ZS_HAVE_FREEBSD ||\ + defined ZS_HAVE_OPENBSD || defined ZS_HAVE_SOLARIS ||\ + defined ZS_HAVE_OSX || defined ZS_HAVE_QNXNTO ||\ + defined ZS_HAVE_HPUX || defined ZS_HAVE_AIX + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <poll.h> +#include <algorithm> + +#include "poll.hpp" +#include "err.hpp" +#include "config.hpp" +#include "i_poll_events.hpp" + +zs::poll_t::poll_t () : + retired (false), + stopping (false) +{ + // Get the limit on open file descriptors. Resize fds so that it + // can hold all descriptors. + rlimit rl; + int rc = getrlimit (RLIMIT_NOFILE, &rl); + errno_assert (rc != -1); + fd_table.resize (rl.rlim_cur); + + for (rlim_t i = 0; i < rl.rlim_cur; i ++) + fd_table [i].index = retired_fd; +} + +zs::handle_t zs::poll_t::add_fd (fd_t fd_, i_poll_events *events_) +{ + pollfd pfd = {fd_, 0, 0}; + pollset.push_back (pfd); + assert (fd_table [fd_].index == retired_fd); + + fd_table [fd_].index = pollset.size() - 1; + fd_table [fd_].events = events_; + + // Increase the load metric of the thread. + load.add (1); + + handle_t handle; + handle.fd = fd_; + return handle; +} + +void zs::poll_t::rm_fd (handle_t handle_) +{ + fd_t index = fd_table [handle_.fd].index; + assert (index != retired_fd); + + // Mark the fd as unused. + pollset [index].fd = retired_fd; + fd_table [handle_.fd].index = retired_fd; + retired = true; + + // Decrease the load metric of the thread. + load.sub (1); +} + +void zs::poll_t::set_pollin (handle_t handle_) +{ + int index = fd_table [handle_.fd].index; + pollset [index].events |= POLLIN; +} + +void zs::poll_t::reset_pollin (handle_t handle_) +{ + int index = fd_table [handle_.fd].index; + pollset [index].events &= ~((short) POLLIN); +} + +void zs::poll_t::set_pollout (handle_t handle_) +{ + int index = fd_table [handle_.fd].index; + pollset [index].events |= POLLOUT; +} + +void zs::poll_t::reset_pollout (handle_t handle_) +{ + int index = fd_table [handle_.fd].index; + pollset [index].events &= ~((short) POLLOUT); +} + +void zs::poll_t::add_timer (i_poll_events *events_) +{ + timers.push_back (events_); +} + +void zs::poll_t::cancel_timer (i_poll_events *events_) +{ + timers_t::iterator it = std::find (timers.begin (), timers.end (), events_); + if (it != timers.end ()) + timers.erase (it); +} + +int zs::poll_t::get_load () +{ + return load.get (); +} + +void zs::poll_t::start () +{ + worker.start (worker_routine, this); +} + +void zs::poll_t::stop () +{ + stopping = true; +} + +void zs::poll_t::join () +{ + worker.stop (); +} + +void zs::poll_t::loop () +{ + while (!stopping) { + + // Wait for events. + int rc = poll (&pollset [0], pollset.size (), + timers.empty () ? -1 : max_timer_period); + if (rc == -1 && errno == EINTR) + continue; + errno_assert (rc != -1); + + // Handle timer. + if (!rc) { + + // Use local list of timers as timer handlers may fill new timers + // into the original array. + timers_t t; + std::swap (timers, t); + + // Trigger all the timers. + for (timers_t::iterator it = t.begin (); it != t.end (); it ++) + (*it)->timer_event (); + + continue; + } + + for (pollset_t::iterator it = pollset.begin (); + it != pollset.end (); it ++) { + + zs_assert (!(it->revents & POLLNVAL)); + if (it->fd == retired_fd) + continue; + if (it->revents & (POLLERR | POLLHUP)) + fd_table [it->fd].events->in_event (); + if (it->fd == retired_fd) + continue; + if (it->revents & POLLOUT) + fd_table [it->fd].events->out_event (); + if (it->fd == retired_fd) + continue; + if (it->revents & POLLIN) + fd_table [it->fd].events->in_event (); + } + + // Clean up the pollset and update the fd_table accordingly. + if (retired) { + pollset_t::size_type i = 0; + while (i < pollset.size ()) { + if (pollset [i].fd == retired_fd) + pollset.erase (pollset.begin () + i); + else { + fd_table [pollset [i].fd].index = i; + i ++; + } + } + retired = false; + } + } +} + +void zs::poll_t::worker_routine (void *arg_) +{ + ((poll_t*) arg_)->loop (); +} + +#endif diff --git a/src/poll.hpp b/src/poll.hpp new file mode 100644 index 0000000..65095f7 --- /dev/null +++ b/src/poll.hpp @@ -0,0 +1,112 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_POLL_HPP_INCLUDED__ +#define __ZS_POLL_HPP_INCLUDED__ + +#include "platform.hpp" + +#if defined ZS_HAVE_LINUX || defined ZS_HAVE_FREEBSD ||\ + defined ZS_HAVE_OPENBSD || defined ZS_HAVE_SOLARIS ||\ + defined ZS_HAVE_OSX || defined ZS_HAVE_QNXNTO ||\ + defined ZS_HAVE_HPUX || defined ZS_HAVE_AIX + +#include <poll.h> +#include <stddef.h> +#include <vector> + +#include "i_poller.hpp" +#include "fd.hpp" +#include "thread.hpp" +#include "atomic_counter.hpp" + +namespace zs +{ + + // Implements socket polling mechanism using the POSIX.1-2001 + // poll() system call. + + class poll_t : public i_poller + { + public: + + poll_t (); + virtual ~poll_t () {} + + // i_poller implementation. + handle_t add_fd (fd_t fd_, i_poll_events *events_); + void rm_fd (handle_t handle_); + void set_pollin (handle_t handle_); + void reset_pollin (handle_t handle_); + void set_pollout (handle_t handle_); + void reset_pollout (handle_t handle_); + void add_timer (i_poll_events *events_); + void cancel_timer (i_poll_events *events_); + int get_load (); + void start (); + void stop (); + void join (); + + private: + + // Main worker thread routine. + static void worker_routine (void *arg_); + + // Main event loop. + void loop (); + + struct fd_entry_t + { + fd_t index; + struct i_poll_events *events; + }; + + // This table stores data for registered descriptors. + std::vector <fd_entry_t> fd_table; + + // Pollset to pass to the poll function. + typedef std::vector <pollfd> pollset_t; + pollset_t pollset; + + // If true, there's at least one retired event source. + bool retired; + + // List of all the engines waiting for the timer event. + typedef std::vector <struct i_poll_events*> timers_t; + timers_t timers; + + // If true, thread is in the process of shutting down. + bool stopping; + + // Handle of the physical thread doing the I/O work. + thread_t worker; + + // Load of the poller. Currently number of file descriptors + // registered with the poller. + atomic_counter_t load; + + poll_t (const poll_t&); + void operator = (const poll_t&); + }; + +} + +#endif + +#endif diff --git a/src/pub.cpp b/src/pub.cpp new file mode 100644 index 0000000..70add18 --- /dev/null +++ b/src/pub.cpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "pub.hpp" +#include "app_thread.hpp" +#include "session.hpp" +#include "err.hpp" + +zs::pub_t::pub_t (app_thread_t *thread_, session_t *session_) : + socket_base_t (thread_, session_) +{ + disable_in (); +} + +int zs::pub_t::recv (struct zs_msg *msg_, int flags_) +{ + // Publisher socket has no recv function. + errno = ENOTSUP; + return -1; +} diff --git a/src/pub.hpp b/src/pub.hpp new file mode 100644 index 0000000..b071318 --- /dev/null +++ b/src/pub.hpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_PUB_HPP_INCLUDED__ +#define __ZS_PUB_HPP_INCLUDED__ + +#include "socket_base.hpp" + +namespace zs +{ + + class pub_t : public socket_base_t + { + public: + + pub_t (class app_thread_t *thread_, class session_t *session_); + + // i_api overloads. + int recv (struct zs_msg *msg_, int flags_); + + private: + + pub_t (const pub_t&); + void operator = (const pub_t&); + }; + +} + +#endif diff --git a/src/rep.cpp b/src/rep.cpp new file mode 100644 index 0000000..586c7ed --- /dev/null +++ b/src/rep.cpp @@ -0,0 +1,29 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "rep.hpp" +#include "app_thread.hpp" +#include "session.hpp" + +zs::rep_t::rep_t (app_thread_t *thread_, session_t *session_) : + socket_base_t (thread_, session_) +{ +} diff --git a/src/rep.hpp b/src/rep.hpp new file mode 100644 index 0000000..e11eaa3 --- /dev/null +++ b/src/rep.hpp @@ -0,0 +1,42 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_REP_HPP_INCLUDED__ +#define __ZS_REP_HPP_INCLUDED__ + +#include "socket_base.hpp" + +namespace zs +{ + + class rep_t : public socket_base_t + { + public: + + rep_t (class app_thread_t *thread_, class session_t *session_); + + private: + + rep_t (const rep_t&); + void operator = (const rep_t&); + }; + +} + +#endif diff --git a/src/req.cpp b/src/req.cpp new file mode 100644 index 0000000..d7a9ad7 --- /dev/null +++ b/src/req.cpp @@ -0,0 +1,29 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "req.hpp" +#include "app_thread.hpp" +#include "session.hpp" + +zs::req_t::req_t (app_thread_t *thread_, session_t *session_) : + socket_base_t (thread_, session_) +{ +} diff --git a/src/req.hpp b/src/req.hpp new file mode 100644 index 0000000..678897d --- /dev/null +++ b/src/req.hpp @@ -0,0 +1,42 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_REQ_HPP_INCLUDED__ +#define __ZS_REQ_HPP_INCLUDED__ + +#include "socket_base.hpp" + +namespace zs +{ + + class req_t : public socket_base_t + { + public: + + req_t (class app_thread_t *thread_, class session_t *session_); + + private: + + req_t (const req_t&); + void operator = (const req_t&); + }; + +} + +#endif diff --git a/src/safe_object.cpp b/src/safe_object.cpp new file mode 100644 index 0000000..a7c72e6 --- /dev/null +++ b/src/safe_object.cpp @@ -0,0 +1,76 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "safe_object.hpp" + +zs::safe_object_t::safe_object_t (class dispatcher_t *dispatcher_, + int thread_slot_) : + object_t (dispatcher_, thread_slot_), + processed_seqnum (0), + terminating (false) +{ +} + +zs::safe_object_t::safe_object_t (object_t *parent_) : + object_t (parent_), + processed_seqnum (0), + terminating (false) +{ +} + +void zs::safe_object_t::inc_seqnum () +{ + // This function is called from the sender thread to ensure that this + // object will still exist when the command sent to it arrives in the + // destination thread. + sent_seqnum.add (1); +} + +void zs::safe_object_t::process_command (struct command_t &cmd_) +{ + object_t::process_command (cmd_); + + // Adjust sequence number of the last processed command. + processed_seqnum++; + + // If we are already in the termination phase and all commands sent to + // this object are processed, it's safe to deallocate it. + if (terminating && sent_seqnum.get () == processed_seqnum) + delete this; +} + +void zs::safe_object_t::terminate () +{ + // Wait till all commands sent to this session are processed. + terminating = true; + + // If there's no pending command we can deallocate the session + // straight saway. + if (sent_seqnum.get () == processed_seqnum) + delete this; +} + +bool zs::safe_object_t::is_terminating () +{ + return terminating; +} + +zs::safe_object_t::~safe_object_t () +{ +} diff --git a/src/safe_object.hpp b/src/safe_object.hpp new file mode 100644 index 0000000..efa0d2f --- /dev/null +++ b/src/safe_object.hpp @@ -0,0 +1,68 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_SAFE_OBJECT_HPP_INCLUDED__ +#define __ZS_SAFE_OBJECT_HPP_INCLUDED__ + +#include "object.hpp" +#include "atomic_counter.hpp" + +namespace zs +{ + + // Same as object_t with the exception of termination mechanism. While + // object_t is destroyed immediately on terminate (assuming that the caller + // have ensured that there are no more commands for the object on the + // fly), safe_object_t switches into termination mode and waits for all + // the on-the-fly commands to be delivered before it deallocates itself. + + class safe_object_t : public object_t + { + public: + + safe_object_t (class dispatcher_t *dispatcher_, int thread_slot_); + safe_object_t (object_t *parent_); + + void inc_seqnum (); + void process_command (struct command_t &cmd_); + + protected: + + void terminate (); + bool is_terminating (); + + virtual ~safe_object_t (); + + private: + + // Sequence number of the last command sent to the object and last + // command processed by the object. The former is an atomic counter + // meaning that other threads can increment it safely. + atomic_counter_t sent_seqnum; + uint32_t processed_seqnum; + + bool terminating; + + safe_object_t (const safe_object_t&); + void operator = (const safe_object_t&); + }; + +} + +#endif diff --git a/src/select.cpp b/src/select.cpp new file mode 100644 index 0000000..9776db3 --- /dev/null +++ b/src/select.cpp @@ -0,0 +1,236 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "platform.hpp" + +#include <string.h> +#include <algorithm> + +#ifdef ZS_HAVE_WINDOWS +#include "winsock2.h" +#elif defined ZS_HAVE_HPUX +#include <sys/param.h> +#include <sys/types.h> +#include <sys/time.h> +#elif defined ZS_HAVE_OPENVMS +#include <sys/types.h> +#include <sys/time.h> +#else +#include <sys/select.h> +#endif + +#include "select.hpp" +#include "err.hpp" +#include "config.hpp" +#include "i_poll_events.hpp" + +zs::select_t::select_t () : + maxfd (retired_fd), + retired (false), + stopping (false) +{ + // Clear file descriptor sets. + FD_ZERO (&source_set_in); + FD_ZERO (&source_set_out); + FD_ZERO (&source_set_err); +} + +zs::handle_t zs::select_t::add_fd (fd_t fd_, i_poll_events *events_) +{ + // Store the file descriptor. + fd_entry_t entry = {fd_, events_}; + fds.push_back (entry); + + // Start polling on errors. + FD_SET (fd_, &source_set_err); + + // Adjust maxfd if necessary. + if (fd_ > maxfd) + maxfd = fd_; + + // Increase the load metric of the thread. + load.add (1); + + handle_t handle; + handle.fd = fd_; + return handle; +} + +void zs::select_t::rm_fd (handle_t handle_) +{ + // Get file descriptor. + fd_t fd = handle_.fd; + + // Mark the descriptor as retired. + fd_set_t::iterator it; + for (it = fds.begin (); it != fds.end (); it ++) + if (it->fd == fd) + break; + zs_assert (it != fds.end ()); + it->fd = retired_fd; + retired = true; + + // Stop polling on the descriptor. + FD_CLR (fd, &source_set_in); + FD_CLR (fd, &source_set_out); + FD_CLR (fd, &source_set_err); + + // Discard all events generated on this file descriptor. + FD_CLR (fd, &readfds); + FD_CLR (fd, &writefds); + FD_CLR (fd, &exceptfds); + + // Adjust the maxfd attribute if we have removed the + // highest-numbered file descriptor. + if (fd == maxfd) { + maxfd = retired_fd; + for (fd_set_t::iterator it = fds.begin (); it != fds.end (); it ++) + if (it->fd > maxfd) + maxfd = it->fd; + } + + // Decrease the load metric of the thread. + load.sub (1); +} + +void zs::select_t::set_pollin (handle_t handle_) +{ + FD_SET (handle_.fd, &source_set_in); +} + +void zs::select_t::reset_pollin (handle_t handle_) +{ + FD_CLR (handle_.fd, &source_set_in); +} + +void zs::select_t::set_pollout (handle_t handle_) +{ + FD_SET (handle_.fd, &source_set_out); +} + +void zs::select_t::reset_pollout (handle_t handle_) +{ + FD_CLR (handle_.fd, &source_set_out); +} + +void zs::select_t::add_timer (i_poll_events *events_) +{ + timers.push_back (events_); +} + +void zs::select_t::cancel_timer (i_poll_events *events_) +{ + timers_t::iterator it = std::find (timers.begin (), timers.end (), events_); + if (it != timers.end ()) + timers.erase (it); +} + +int zs::select_t::get_load () +{ + return load.get (); +} + +void zs::select_t::start () +{ + worker.start (worker_routine, this); +} + +void zs::select_t::stop () +{ + stopping = true; +} + +void zs::select_t::join () +{ + worker.stop (); +} + +void zs::select_t::loop () +{ + while (!stopping) { + + // Intialise the pollsets. + memcpy (&readfds, &source_set_in, sizeof source_set_in); + memcpy (&writefds, &source_set_out, sizeof source_set_out); + memcpy (&exceptfds, &source_set_err, sizeof source_set_err); + + // Compute the timout interval. Select is free to overwrite the + // value so we have to compute it each time anew. + timeval timeout = {max_timer_period / 1000, + (max_timer_period % 1000) * 1000}; + + // Wait for events. + int rc = select (maxfd + 1, &readfds, &writefds, &exceptfds, + timers.empty () ? NULL : &timeout); + +#ifdef ZS_HAVE_WINDOWS + wsa_assert (rc != SOCKET_ERROR); +#else + if (rc == -1 && errno == EINTR) + continue; + errno_assert (rc != -1); +#endif + + // Handle timer. + if (!rc) { + + // Use local list of timers as timer handlers may fill new timers + // into the original array. + timers_t t; + std::swap (timers, t); + + // Trigger all the timers. + for (timers_t::iterator it = t.begin (); it != t.end (); it ++) + (*it)->timer_event (); + + continue; + } + + for (fd_set_t::size_type i = 0; i < fds.size (); i ++) { + if (fds [i].fd == retired_fd) + continue; + if (FD_ISSET (fds [i].fd, &exceptfds)) + fds [i].events->in_event (); + if (fds [i].fd == retired_fd) + continue; + if (FD_ISSET (fds [i].fd, &writefds)) + fds [i].events->out_event (); + if (fds [i].fd == retired_fd) + continue; + if (FD_ISSET (fds [i].fd, &readfds)) + fds [i].events->in_event (); + } + + // Destroy retired event sources. + if (retired) { + for (fd_set_t::size_type i = 0; i < fds.size (); i ++) { + if (fds [i].fd == retired_fd) { + fds.erase (fds.begin () + i); + i --; + } + } + retired = false; + } + } +} + +void zs::select_t::worker_routine (void *arg_) +{ + ((select_t*) arg_)->loop (); +} diff --git a/src/select.hpp b/src/select.hpp new file mode 100644 index 0000000..7151f84 --- /dev/null +++ b/src/select.hpp @@ -0,0 +1,122 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_SELECT_HPP_INCLUDED__ +#define __ZS_SELECT_HPP_INCLUDED__ + +#include "platform.hpp" + +#include <stddef.h> +#include <vector> + +#ifdef ZS_HAVE_WINDOWS +#include "winsock2.h" +#elif defined ZS_HAVE_OPENVMS +#include <sys/types.h> +#include <sys/time.h> +#else +#include <sys/select.h> +#endif + +#include "i_poller.hpp" +#include "fd.hpp" +#include "thread.hpp" +#include "atomic_counter.hpp" + +namespace zs +{ + + // Implements socket polling mechanism using POSIX.1-2001 select() + // function. + + class select_t : public i_poller + { + public: + + select_t (); + + // i_poller implementation. + handle_t add_fd (fd_t fd_, i_poll_events *events_); + void rm_fd (handle_t handle_); + void set_pollin (handle_t handle_); + void reset_pollin (handle_t handle_); + void set_pollout (handle_t handle_); + void reset_pollout (handle_t handle_); + void add_timer (i_poll_events *events_); + void cancel_timer (i_poll_events *events_); + int get_load (); + void start (); + void stop (); + void join (); + + private: + + // Main worker thread routine. + static void worker_routine (void *arg_); + + // Main event loop. + void loop (); + + struct fd_entry_t + { + fd_t fd; + struct i_poll_events *events; + }; + + // Set of file descriptors that are used to retreive + // information for fd_set. + typedef std::vector <fd_entry_t> fd_set_t; + fd_set_t fds; + + fd_set source_set_in; + fd_set source_set_out; + fd_set source_set_err; + + fd_set readfds; + fd_set writefds; + fd_set exceptfds; + + // Maximum file descriptor. + fd_t maxfd; + + // If true, at least one file descriptor has retired. + bool retired; + + // List of all the engines waiting for the timer event. + typedef std::vector <struct i_poll_events*> timers_t; + timers_t timers; + + // If true, thread is shutting down. + bool stopping; + + // Handle of the physical thread doing the I/O work. + thread_t worker; + + // Load of the poller. Currently number of file descriptors + // registered with the poller. + atomic_counter_t load; + + select_t (const select_t&); + void operator = (const select_t&); + }; + +} + +#endif + diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..63868b2 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,273 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "session.hpp" +#include "i_engine.hpp" +#include "i_thread.hpp" +#include "i_mux.hpp" +#include "i_demux.hpp" +#include "err.hpp" +#include "pipe.hpp" +#include "pipe_reader.hpp" +#include "pipe_writer.hpp" +#include "simple_semaphore.hpp" + +zs::session_t::session_t (object_t *parent_, i_thread *thread_, + i_mux *mux_, i_demux *demux_, + bool terminate_on_disconnect_, bool terminate_on_no_pipes_) : + safe_object_t (parent_), + mux (mux_), + demux (demux_), + thread (thread_), + engine (NULL), + terminate_on_disconnect (terminate_on_disconnect_), + terminate_on_no_pipes (false), + terminate_on_no_pipes_delayed (terminate_on_no_pipes_), + index (-1) +{ + // At least one way to terminate the session should be allowed. Otherwise + // the session can be orphaned forever. + zs_assert (terminate_on_disconnect || terminate_on_no_pipes_delayed); + + // Give the mux and the demux callback pointer to ourselves. + if (mux) + mux->set_session (this); + if (demux) + demux->set_session (this); +} + +void zs::session_t::shutdown () +{ + // Session may live even without an associated engine, thus we have + // to check if for NULL value. + if (engine) + engine->shutdown (); + + // Propagate the shutdown signal to both inbound and outbound pipes. + if (mux) + mux->shutdown (); + if (demux) + demux->shutdown (); + + delete this; +} + +void zs::session_t::disconnected () +{ + // It's engine who calls this function so there's no need to deallocate + // the engine. Just drop the reference. + engine = NULL; + + // Some sessions won't shut down because of disconnect. New engine will + // attached to the session later on. + if (!terminate_on_disconnect) + return; + + terminate (); +} + +void zs::session_t::bind (object_t *peer_, bool in_, bool out_) +{ + // Create the out pipe (if required). + pipe_reader_t *pipe_reader = NULL; + if (out_) { + pipe_writer_t *pipe_writer; + create_pipe (peer_, this, 0, 0, &pipe_reader, &pipe_writer); + demux->attach_pipe (pipe_writer); + + // There's at least one pipe attached. We can deallocate the object + // when there are no pipes (if required). + terminate_on_no_pipes = terminate_on_no_pipes_delayed; + } + + // Ask peer to attach to the out pipe (if one exists). If required, ask + // it to create a pipe in opposite direction. It's assumed that peer's + // seqnum was already incremented, so we don't need to care whether it's + // alive at the moment. + if (in_) + inc_seqnum (); + send_bind (peer_, pipe_reader, in_ ? this : NULL); +} + +void zs::session_t::revive () +{ + if (engine) + engine->revive (); +} + +void zs::session_t::terminate () +{ + // Terminate is always called by engine, thus it'll terminate itself, + // we just have to drop the pointer. + engine = NULL; + + // Propagate the terminate signal to both inbound and outbound pipes. + if (mux) { + mux->terminate (); + mux = NULL; + } + if (demux) { + demux->terminate (); + demux = NULL; + } + + // Session cannot be deallocated at this point. There can still be + // pending commands to process. Unregister session from global + // repository thus ensuring that no new commands will be sent. + unregister_inproc_endpoints (this); + + // Move to terminating state. + safe_object_t::terminate (); +} + +zs::session_t::~session_t () +{ + // When session is actually deallocated it unregisters from its thread. + // Unregistration cannot be done earlier as it would result in memory + // leak if global shutdown happens in the middle of session termination. + thread->detach_session (this); +} + +void zs::session_t::set_engine (i_engine *engine_) +{ + zs_assert (!engine || !engine_); + engine = engine_; +} + +void zs::session_t::set_index (int index_) +{ + index = index_; +} + +int zs::session_t::get_index () +{ + return index; +} + +bool zs::session_t::write (zs_msg *msg_) +{ + return demux->send (msg_); +} + +void zs::session_t::flush () +{ + demux->flush (); +} + +bool zs::session_t::read (zs_msg *msg_) +{ + bool retrieved = mux->recv (msg_); + if (terminate_on_no_pipes && mux->empty () && demux->empty ()) { + zs_assert (engine); + engine->schedule_terminate (); + terminate (); + } + return retrieved; +} + +void zs::session_t::process_bind (pipe_reader_t *reader_, session_t *peer_) +{ + if (is_terminating ()) { + + // If session is already in termination phase, we'll ask newly arrived + // pipe reader & writer to terminate straight away. + if (reader_) + reader_->terminate (); + + // Peer session has already incremented its seqnum. We have to send + // a dummy command to avoid a memory leak. + if (peer_) + send_bind (peer_, NULL, NULL); + + return; + } + + // If inbound pipe is provided, bind it to the mux. + if (reader_) { + mux->attach_pipe (reader_); + + // There's at least one pipe attached. We can deallocate the object + // when there are no pipes (if required). + terminate_on_no_pipes = terminate_on_no_pipes_delayed; + } + + // If peer wants to get messages from ourselves, we'll bind to it. + if (peer_) { + pipe_reader_t *pipe_reader; + pipe_writer_t *pipe_writer; + create_pipe (peer_, this, 0, 0, &pipe_reader, &pipe_writer); + demux->attach_pipe (pipe_writer); + send_bind (peer_, pipe_reader, NULL); + + // There's at least one pipe attached. We can deallocate the object + // when there are no pipes (if required). + terminate_on_no_pipes = terminate_on_no_pipes_delayed; + } +} + +void zs::session_t::process_reg (simple_semaphore_t *smph_) +{ + zs_assert (!is_terminating ()); + + // Add the session to the list of sessions associated with this I/O thread. + // This way the session will be deallocated on the terminal shutdown. + thread->attach_session (this); + + // Release calling thead (if required). + if (smph_) + smph_->post (); +} + +void zs::session_t::process_reg_and_bind (session_t *peer_, + bool flow_in_, bool flow_out_) +{ + zs_assert (!is_terminating ()); + + // Add the session to the list of sessions associated with this I/O thread. + // This way the session will be deallocated on the terminal shutdown. + thread->attach_session (this); + + // Bind to the peer. Note that caller have already incremented command + // sequence number of the peer so we are sure it still exists. + pipe_reader_t *pipe_reader = NULL; + if (flow_out_) { + pipe_writer_t *pipe_writer; + create_pipe (peer_, this, 0, 0, &pipe_reader, &pipe_writer); + demux->attach_pipe (pipe_writer); + + // There's at least one pipe attached. We can deallocate the object + // when there are no pipes (if required). + terminate_on_no_pipes = terminate_on_no_pipes_delayed; + } + send_bind (peer_, pipe_reader, flow_in_ ? this : NULL); +} + +void zs::session_t::process_engine (i_engine *engine_) +{ + if (is_terminating ()) { + + // Kill the engine. It won't be needed anymore. + engine_->terminate (); + return; + } + + engine_->attach (thread->get_poller (), this); +} diff --git a/src/session.hpp b/src/session.hpp new file mode 100644 index 0000000..3cdace2 --- /dev/null +++ b/src/session.hpp @@ -0,0 +1,107 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_SESSION_HPP_INCLUDED__ +#define __ZS_SESSION_HPP_INCLUDED__ + +#include "i_session.hpp" +#include "safe_object.hpp" +#include "stdint.hpp" +#include "atomic_counter.hpp" + +namespace zs +{ + + // Object that encapsulates both mux and demux. + + class session_t : public safe_object_t, public i_session + { + public: + + // Creates the session object. + session_t (struct object_t *parent_, struct i_thread *thread_, + struct i_mux *mux_, struct i_demux *demux_, + bool terminate_on_disconnect_, bool terminate_on_no_pipes_); + + // i_session implementation + void set_engine (struct i_engine *engine_); + void shutdown (); + bool read (struct zs_msg *msg_); + bool write (struct zs_msg *msg_); + void flush (); + + // Called by the engine when it is being closed. + void disconnected (); + + // Creates a message flow between this session and the peer session. + // If in_ is true, the messages can flow from the peer to ourselves. + // If out_ is true, messages can flow from ourselves to the peer. + // It's assumed that peer's seqnum was already incremented. + void bind (class object_t *peer_, bool in_, bool out_); + + // Called by mux if new messages are available. + void revive (); + + // Functions to set & retrieve index of this MD in thread's array + // of session objects. + void set_index (int index_); + int get_index (); + + private: + + // Clean-up. + ~session_t (); + + // Terminate is private here. It is called by either when disconnected + // or no_pipes event occurs. + void terminate (); + + void process_bind (class pipe_reader_t *reader_, + class session_t *peer_); + void process_reg (class simple_semaphore_t *smph_); + void process_reg_and_bind (class session_t *peer_, + bool flow_in_, bool flow_out_); + void process_engine (i_engine *engine_); + + struct i_mux *mux; + struct i_demux *demux; + + struct i_thread *thread; + struct i_engine *engine; + + // If true termination of the session can be triggered by engine + // disconnect/close. + bool terminate_on_disconnect; + + // If true termination of the session can be triggered when the last + // pipe detaches from it. + bool terminate_on_no_pipes; + + // If true, terminate_on_no_pipes should be set when at least one + // pipe was bound. + bool terminate_on_no_pipes_delayed; + + // Index in thread's session array. + int index; + }; + +} + +#endif + diff --git a/src/session_stub.cpp b/src/session_stub.cpp new file mode 100644 index 0000000..3bebcb0 --- /dev/null +++ b/src/session_stub.cpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string> + +#include "../include/zs.h" + +#include "session_stub.hpp" +#include "i_engine.hpp" +#include "listener.hpp" +#include "err.hpp" + +zs::session_stub_t::session_stub_t (listener_t *listener_) : + state (reading_identity), + engine (NULL), + listener (listener_), + index (-1) +{ +} + +void zs::session_stub_t::terminate () +{ + if (engine) + engine->terminate (); + delete this; +} + +void zs::session_stub_t::shutdown () +{ + if (engine) + engine->shutdown (); + delete this; +} + +zs::session_stub_t::~session_stub_t () +{ +} + +void zs::session_stub_t::set_engine (i_engine *engine_) +{ + zs_assert (!engine_ || !engine); + engine = engine_; +} + +bool zs::session_stub_t::read (struct zs_msg *msg_) +{ + // No messages are sent to the connecting peer. + return false; +} + +bool zs::session_stub_t::write (struct zs_msg *msg_) +{ + // The first message arrived is the connection identity. + if (state == reading_identity) { + identity = std::string ((const char*) zs_msg_data (msg_), + zs_msg_size (msg_)); + state = has_identity; + return true; + } + + // We are not interested in any subsequent messages. + return false; +} + +void zs::session_stub_t::flush () +{ + // We have the identity. At this point we can find the correct session and + // attach it to the connection. + if (state == has_identity) { + + // At this point the stub will be deleted. Return immediately without + // touching 'this' pointer. + listener->got_identity (this, identity.c_str ()); + return; + } +} + +zs::i_engine *zs::session_stub_t::detach_engine () +{ + // Ask engine to unregister from the poller. + i_engine *e = engine; + engine->detach (); + return e; +} + +void zs::session_stub_t::set_index (int index_) +{ + index = index_; +} + +int zs::session_stub_t::get_index () +{ + return index; +} diff --git a/src/session_stub.hpp b/src/session_stub.hpp new file mode 100644 index 0000000..2e882f8 --- /dev/null +++ b/src/session_stub.hpp @@ -0,0 +1,83 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_SESSION_STUB_HPP_INCLUDED__ +#define __ZS_SESSION_STUB_HPP_INCLUDED__ + +#include <string> + +#include "i_session.hpp" + +namespace zs +{ + + // This class is used instead of regular session till the identity of + // incomming connection is established and connection is attached + // to corresponding session. + + class session_stub_t : public i_session + { + public: + + session_stub_t (class listener_t *listener_); + + // i_session implementation. + void set_engine (struct i_engine *engine_); + void terminate (); + void shutdown (); + bool read (struct zs_msg *msg_); + bool write (struct zs_msg *msg_); + void flush (); + + // Detaches engine from the stub. Returns it to the caller. + struct i_engine *detach_engine (); + + // Manipulate stubs's index in listener's array of stubs. + void set_index (int index_); + int get_index (); + + private: + + // Clean-up. + virtual ~session_stub_t (); + + enum { + reading_identity, + has_identity + } state; + + // Reference to the associated engine. + i_engine *engine; + + // Reference to the listener object that owns this stub. + class listener_t *listener; + + // Index of the stub in listener's array of stubs. + int index; + + // Identity of the connection. + std::string identity; + + session_stub_t (const session_stub_t&); + void operator = (const session_stub_t&); + }; + +} + +#endif diff --git a/src/simple_semaphore.hpp b/src/simple_semaphore.hpp new file mode 100644 index 0000000..1bd114f --- /dev/null +++ b/src/simple_semaphore.hpp @@ -0,0 +1,188 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_SIMPLE_SEMAPHORE_HPP_INCLUDED__ +#define __ZS_SIMPLE_SEMAPHORE_HPP_INCLUDED__ + +#include "platform.hpp" +#include "err.hpp" + +#if defined ZS_HAVE_LINUX || defined ZS_HAVE_OSX || defined ZS_HAVE_OPENVMS +#include <pthread.h> +#elif defined ZS_HAVE_WINDOWS +#include "windows.hpp" +#else +#include <semaphore.h> +#endif + +namespace zs +{ + + // Simple semaphore. Only single thread may be waiting at any given time. + // Also, the semaphore may not be posted before the previous post + // was matched by corresponding wait and the waiting thread was + // released. + +#if defined ZS_HAVE_LINUX || defined ZS_HAVE_OSX || defined ZS_HAVE_OPENVMS + + // On platforms that allow for double locking of a mutex from the same + // thread, simple semaphore is implemented using mutex, as it is more + // efficient than full-blown semaphore. + + class simple_semaphore_t + { + public: + + // Initialise the semaphore. + inline simple_semaphore_t () + { + int rc = pthread_mutex_init (&mutex, NULL); + errno_assert (rc == 0); + rc = pthread_mutex_lock (&mutex); + errno_assert (rc == 0); + } + + // Destroy the semaphore. + inline ~simple_semaphore_t () + { + int rc = pthread_mutex_unlock (&mutex); + errno_assert (rc == 0); + rc = pthread_mutex_destroy (&mutex); + errno_assert (rc == 0); + } + + // Wait for the semaphore. + inline void wait () + { + int rc = pthread_mutex_lock (&mutex); + errno_assert (rc == 0); + } + + // Post the semaphore. + inline void post () + { + int rc = pthread_mutex_unlock (&mutex); + errno_assert (rc == 0); + } + + private: + + pthread_mutex_t mutex; + + // Disable copying of the object. + simple_semaphore_t (const simple_semaphore_t&); + void operator = (const simple_semaphore_t&); + }; + +#elif defined ZMQ_HAVE_WINDOWS + + // On Windows platform simple semaphore is implemeted using event object. + + class simple_semaphore_t + { + public: + + // Initialise the semaphore. + inline simple_semaphore_t () + { + ev = CreateEvent (NULL, FALSE, FALSE, NULL); + win_assert (ev != NULL); + } + + // Destroy the semaphore. + inline ~simple_semaphore_t () + { + int rc = CloseHandle (ev); + win_assert (rc != 0); + } + + // Wait for the semaphore. + inline void wait () + { + DWORD rc = WaitForSingleObject (ev, INFINITE); + win_assert (rc != WAIT_FAILED); + } + + // Post the semaphore. + inline void post () + { + int rc = SetEvent (ev); + win_assert (rc != 0); + } + + private: + + HANDLE ev; + + // Disable copying of the object. + simple_semaphore_t (const simple_semaphore_t&); + void operator = (const simple_semaphore_t&); + }; + +#else + + // Default implementation maps simple semaphore to standard semaphore. + + class simple_semaphore_t + { + public: + + // Initialise the semaphore. + inline simple_semaphore_t () + { + int rc = sem_init (&sem, 0, 0); + errno_assert (rc != -1); + } + + // Destroy the semaphore. + inline ~simple_semaphore_t () + { + int rc = sem_destroy (&sem); + errno_assert (rc != -1); + } + + // Wait for the semaphore. + inline void wait () + { + int rc = sem_wait (&sem); + errno_assert (rc != -1); + } + + // Post the semaphore. + inline void post () + { + int rc = sem_post (&sem); + errno_assert (rc != -1); + } + + private: + + // Underlying system semaphore object. + sem_t sem; + + // Disable copying of the object. + simple_semaphore_t (const simple_semaphore_t&); + void operator = (const simple_semaphore_t&); + }; + +#endif + +} + +#endif diff --git a/src/socket_base.cpp b/src/socket_base.cpp new file mode 100644 index 0000000..07606ad --- /dev/null +++ b/src/socket_base.cpp @@ -0,0 +1,267 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string> + +#include "../include/zs.h" + +#include "socket_base.hpp" +#include "app_thread.hpp" +#include "err.hpp" +#include "listener.hpp" +#include "connecter.hpp" +#include "simple_semaphore.hpp" +#include "io_thread.hpp" +#include "io_object.hpp" +#include "session.hpp" +#include "dummy_aggregator.hpp" +#include "dummy_distributor.hpp" + +zs::socket_base_t::socket_base_t (app_thread_t *thread_, session_t *session_) : + object_t (thread_), + thread (thread_), + session (session_), + has_in (true), + has_out (true) +{ + session->set_engine (this); +} + +void zs::socket_base_t::shutdown () +{ + // Destroy all the I/O objects created from this socket. + for (io_objects_t::size_type i = 0; i != io_objects.size (); i++) + io_objects [i]->shutdown (); + + delete this; +} + +void zs::socket_base_t::schedule_terminate () +{ + // Terminate is never scheduled on socket engines. + zs_assert (false); +} + +void zs::socket_base_t::terminate () +{ + // Destroy all the I/O objects created from this socket. + // First unregister the object from I/O thread, then terminate it in + // this application thread. + simple_semaphore_t smph; + for (io_objects_t::size_type i = 0; i != io_objects.size (); i++) { + send_unreg (io_objects [i], &smph); + smph.wait (); + io_objects [i]->terminate (); + } + + zs_assert (session); + session->disconnected (); + + delete this; +} + +zs::socket_base_t::~socket_base_t () +{ +} + +void zs::socket_base_t::disable_in () +{ + has_in = false; +} + +void zs::socket_base_t::disable_out () +{ + has_out = false; +} + +int zs::socket_base_t::bind (const char *addr_, zs_opts *opts_) +{ + thread->process_commands (false); + + std::string addr (addr_); + std::string::size_type pos = addr.find ("://"); + if (pos == std::string::npos || addr.substr (0, pos) == "zmq.tcp") { + + // Choose the I/O thread with the least load, create the listener. + // Note that same taskset is used to choose the I/O thread to handle + // the listening socket and newly created connections. + // Note that has_in and has_out are twisted at this place - listener + // is going to create peer objects, so the message flows are viewed + // from the opposite direction. + io_thread_t *io_thread = choose_io_thread (opts_ ? opts_->taskset : 0); + listener_t *listener = new listener_t (io_thread, addr_, session, + has_out, has_in, opts_ ? opts_->taskset : 0); + + // Ask it to start interacting with the I/O thread. + simple_semaphore_t smph; + send_reg (listener, &smph); + + // Store the reference to the listener so that it can be terminated + // when the socket is closed. + io_objects.push_back (listener); + + // Wait while listener is actually registered with the I/O thread. + smph.wait (); + + return 0; + } + else if (addr.substr (0, pos) == "inproc") { + + // For inproc transport the only thing we have to do is to register + // this socket as an inproc endpoint with the supplied name. + return register_inproc_endpoint (addr.substr (pos + 3).c_str (), + session); + } + else { + + // Unknown protocol requested. + errno = EINVAL; + return -1; + } +} + +int zs::socket_base_t::connect (const char *addr_, zs_opts *opts_) +{ + thread->process_commands (false); + + std::string addr (addr_); + std::string::size_type pos = addr.find ("://"); + if (pos == std::string::npos || addr.substr (0, pos) == "zmq.tcp") { + + // Choose the I/O thread with the least load, create the connecter and + // session. + io_thread_t *io_thread = choose_io_thread (opts_ ? opts_->taskset : 0); + i_mux *mux = new dummy_aggregator_t; + zs_assert (mux); + i_demux *demux = new dummy_distributor_t; + zs_assert (demux); + session_t *peer = new session_t (io_thread, io_thread, mux, demux, + false, true); + zs_assert (peer); + connecter_t *connecter = new connecter_t (io_thread, addr_, peer); + zs_assert (connecter); + + // Increment session's command sequence number so that it won't get + // deallocated till the subsequent bind command arrives. + peer->inc_seqnum (); + + // Register the connecter (and session) with its I/O thread. + simple_semaphore_t smph; + send_reg (connecter, &smph); + + // Store the reference to the connecter so that it can be terminated + // when the socket is closed. + io_objects.push_back (connecter); + + // Wait till registration succeeds. + smph.wait (); + + // Bind local session with the connecter's session so that messages + // can flow in both directions. + session->bind (peer, has_in, has_out); + + return 0; + } + else if (addr.substr (0, pos) == "inproc") { + + // Get the MD responsible for the address. In case of invalid address + // return error. + object_t *peer = get_inproc_endpoint (addr.substr (pos + 3).c_str ()); + if (!peer) { + errno = EADDRNOTAVAIL; + return -1; + } + + // Create bidirectional message pipes between this session and + // the peer session. + session->bind (peer, has_in, has_out); + + return 0; + } + else { + + // Unknown protocol requested. + errno = EINVAL; + return -1; + } +} + +int zs::socket_base_t::subscribe (const char *criteria_) +{ + // No implementation at the moment... + errno = ENOTSUP; + return -1; +} + +int zs::socket_base_t::send (zs_msg *msg_, int flags_) +{ + thread->process_commands (false); + while (true) { + if (session->write (msg_)) + return 0; + if (flags_ & ZS_NOBLOCK) { + errno = EAGAIN; + return -1; + } + thread->process_commands (true); + } +} + +int zs::socket_base_t::flush () +{ + thread->process_commands (false); + session->flush (); + return 0; +} + +int zs::socket_base_t::recv (zs_msg *msg_, int flags_) +{ + thread->process_commands (false); + while (true) { + if (session->read (msg_)) + return 0; + if (flags_ & ZS_NOBLOCK) { + errno = EAGAIN; + return -1; + } + thread->process_commands (true); + } +} + +int zs::socket_base_t::close () +{ + terminate (); + return 0; +} + +void zs::socket_base_t::attach (struct i_poller *poller_, i_session *session_) +{ + zs_assert (false); +} + +void zs::socket_base_t::detach () +{ + zs_assert (false); +} + +void zs::socket_base_t::revive () +{ + // We can ignore the event safely here. +} + diff --git a/src/socket_base.hpp b/src/socket_base.hpp new file mode 100644 index 0000000..ed0272a --- /dev/null +++ b/src/socket_base.hpp @@ -0,0 +1,96 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_SOCKET_BASE_HPP_INCLUDED__ +#define __ZS_SOCKET_BASE_HPP_INCLUDED__ + +#include <vector> + +#include "i_engine.hpp" +#include "i_api.hpp" +#include "object.hpp" + +namespace zs +{ + + class socket_base_t : public object_t, public i_engine, public i_api + { + public: + + // TODO: Possibly, session can be attached to the engine using + // attach function. + socket_base_t (class app_thread_t *thread_, class session_t *session_); + + // i_engine interface implementation. + void attach (struct i_poller *poller_, struct i_session *session_); + void detach (); + void revive (); + void schedule_terminate (); + void terminate (); + void shutdown (); + + // i_api interface implementation. + int bind (const char *addr_, struct zs_opts *opts_); + int connect (const char *addr_, struct zs_opts *opts_); + int subscribe (const char *criteria_); + int send (struct zs_msg *msg_, int flags_); + int flush (); + int recv (struct zs_msg *msg_, int flags_); + int close (); + + protected: + + // Clean-up. The function has to be protected rather than private, + // otherwise auto-generated destructor in derived classes + // cannot be compiled. It has to be virtual so that socket_base_t's + // terminate & shutdown functions deallocate correct type of object. + virtual ~socket_base_t (); + + // By default, socket is able to pass messages in both inward and + // outward directions. By calling these functions, particular + // socket type is able to eliminate one direction. + void disable_in (); + void disable_out (); + + private: + + // Pointer to the application thread the socket belongs to. + class app_thread_t *thread; + + // Pointer to the associated session object. + class session_t *session; + + // List of I/O object created via this socket. These have to be shut + // down when the socket is closed. + typedef std::vector <class io_object_t*> io_objects_t; + io_objects_t io_objects; + + // If true, socket creates inbound pipe when binding to an engine. + bool has_in; + + // If true, socket creates outbound pipe when binding to an engine. + bool has_out; + + socket_base_t (const socket_base_t&); + void operator = (const socket_base_t&); + }; + +} + +#endif diff --git a/src/stdint.hpp b/src/stdint.hpp new file mode 100644 index 0000000..b4c6125 --- /dev/null +++ b/src/stdint.hpp @@ -0,0 +1,70 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_STDINT_HPP_INCLUDED__ +#define __ZS_STDINT_HPP_INCLUDED__ + +#include "platform.hpp" + +#ifdef ZS_HAVE_SOLARIS + +#include <inttypes.h> + +#elif defined _MSC_VER + +#ifndef int8_t +typedef __int8 int8_t; +#endif +#ifndef int16_t +typedef __int16 int16_t; +#endif +#ifndef int32_t +typedef __int32 int32_t; +#endif +#ifndef int64_t +typedef __int64 int64_t; +#endif +#ifndef uint8_t +typedef unsigned __int8 uint8_t; +#endif +#ifndef uint16_t +typedef unsigned __int16 uint16_t; +#endif +#ifndef uint32_t +typedef unsigned __int32 uint32_t; +#endif +#ifndef uint64_t +typedef unsigned __int64 uint64_t; +#endif + +#elif defined ZMQ_HAVE_OPENVMS + +#include <types.h> +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; + +#else + +#include <stdint.h> + +#endif + +#endif diff --git a/src/sub.cpp b/src/sub.cpp new file mode 100644 index 0000000..59e838a --- /dev/null +++ b/src/sub.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include "sub.hpp" +#include "app_thread.hpp" +#include "session.hpp" +#include "err.hpp" + +zs::sub_t::sub_t (app_thread_t *thread_, session_t *session_) : + socket_base_t (thread_, session_) +{ + disable_out (); +} + +int zs::sub_t::send (struct zs_msg *msg_, int flags_) +{ + // Subscriber socket has no send function. + errno = ENOTSUP; + return -1; +} + +int zs::sub_t::flush () +{ + // Subscriber socket has no flush function. + errno = ENOTSUP; + return -1; +} diff --git a/src/sub.hpp b/src/sub.hpp new file mode 100644 index 0000000..b7c2bd2 --- /dev/null +++ b/src/sub.hpp @@ -0,0 +1,46 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_SUB_HPP_INCLUDED__ +#define __ZS_SUB_HPP_INCLUDED__ + +#include "socket_base.hpp" + +namespace zs +{ + + class sub_t : public socket_base_t + { + public: + + sub_t (class app_thread_t *thread_, class session_t *session_); + + // i_api overloads. + int send (struct zs_msg *msg_, int flags_); + int flush (); + + private: + + sub_t (const sub_t&); + void operator = (const sub_t&); + }; + +} + +#endif diff --git a/src/tcp_connecter.cpp b/src/tcp_connecter.cpp new file mode 100644 index 0000000..6b59290 --- /dev/null +++ b/src/tcp_connecter.cpp @@ -0,0 +1,138 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "tcp_connecter.hpp" +#include "platform.hpp" +#include "ip.hpp" +#include "err.hpp" + +#ifdef ZS_HAVE_WINDOWS + +#include "windows.hpp" +#error + +#else + +#include <unistd.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fcntl.h> + +zs::tcp_connecter_t::tcp_connecter_t () : + s (retired_fd) +{ +} + +zs::tcp_connecter_t::~tcp_connecter_t () +{ + if (s != retired_fd) + close (); +} + +int zs::tcp_connecter_t::open (const char *addr_) +{ + zs_assert (s == retired_fd); + + // Convert the hostname into sockaddr_in structure. + sockaddr_in address; + int rc = resolve_ip_hostname (&address, addr_); + if (rc != 0) + return -1; + + // Create the socket. + s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) + return -1; + + // Set to non-blocking mode. + int flags = fcntl (s, F_GETFL, 0); + if (flags == -1) + flags = 0; + rc = fcntl (s, F_SETFL, flags | O_NONBLOCK); + errno_assert (rc != -1); + + // Disable Nagle's algorithm. + int flag = 1; + rc = setsockopt (s, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof (int)); + errno_assert (rc == 0); + +#ifdef ZS_HAVE_OPENVMS + // Disable delayed acknowledgements. + flag = 1; + rc = setsockopt (s, IPPROTO_TCP, TCP_NODELACK, (char*) &flag, sizeof (int)); + errno_assert (rc != SOCKET_ERROR); +#endif + + // Connect to the remote peer. + rc = ::connect (s, (sockaddr*) &address, sizeof address); + + // Connect was successfull immediately. + if (rc == 0) + return 0; + + // Asynchronous connect was launched. + if (rc == -1 && errno == EINPROGRESS) + return 1; + + // Error occured. + int err = errno; + close (); + errno = err; + return -1; +} + +int zs::tcp_connecter_t::close () +{ + zs_assert (s != retired_fd); + int rc = ::close (s); + if (rc != 0) + return -1; + s = retired_fd; + return 0; +} + +zs::fd_t zs::tcp_connecter_t::get_fd () +{ + return s; +} + +zs::fd_t zs::tcp_connecter_t::connect () +{ + // Following code should handle both Berkeley-derived socket + // implementations and Solaris. + int err = 0; + socklen_t len = sizeof err; + int rc = getsockopt (s, SOL_SOCKET, SO_ERROR, (char*) &err, &len); + if (rc == -1) + err = errno; + if (err != 0) { + close (); + errno = err; + return retired_fd; + } + + fd_t result = s; + s = retired_fd; + return result; +} + +#endif diff --git a/src/tcp_connecter.hpp b/src/tcp_connecter.hpp new file mode 100644 index 0000000..ef11242 --- /dev/null +++ b/src/tcp_connecter.hpp @@ -0,0 +1,65 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_TCP_CONNECTER_HPP_INCLUDED__ +#define __ZS_TCP_CONNECTER_HPP_INCLUDED__ + +#include "fd.hpp" + +namespace zs +{ + + // The class encapsulating simple TCP listening socket. + + class tcp_connecter_t + { + public: + + tcp_connecter_t (); + ~tcp_connecter_t (); + + // Open TCP connecting socket. Address is in + // <hostname>:<port-number> format. Returns -1 in case of error, + // 0 if connect was successfull immediately and 1 if async connect + // was launched. + int open (const char *addr_); + + // Close the connecting socket. + int close (); + + // Get the file descriptor to poll on to get notified about + // connection success. + fd_t get_fd (); + + // Get the file descriptor of newly created connection. Returns + // retired_fd if the connection was unsuccessfull. + fd_t connect (); + + private: + + // Underlying socket. + fd_t s; + + tcp_connecter_t (const tcp_connecter_t&); + void operator = (const tcp_connecter_t&); + }; + +} + +#endif diff --git a/src/tcp_listener.cpp b/src/tcp_listener.cpp new file mode 100644 index 0000000..3703950 --- /dev/null +++ b/src/tcp_listener.cpp @@ -0,0 +1,165 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "tcp_listener.hpp" +#include "platform.hpp" +#include "ip.hpp" +#include "config.hpp" +#include "err.hpp" + +#ifdef ZS_HAVE_WINDOWS + +#include "windows.hpp" +#error + +#else + +#include <unistd.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fcntl.h> + +zs::tcp_listener_t::tcp_listener_t () : + s (retired_fd) +{ +} + +zs::tcp_listener_t::~tcp_listener_t () +{ + if (s != retired_fd) + close (); +} + +int zs::tcp_listener_t::open (const char *addr_) +{ + // Convert the interface into sockaddr_in structure. + sockaddr_in ip_address; + int rc = resolve_ip_interface (&ip_address, addr_); + if (rc != 0) + return -1; + + // Create a listening socket. + s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) + return -1; + + // Allow reusing of the address. + int flag = 1; + rc = setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (int)); + errno_assert (rc == 0); + + // Set the non-blocking flag. + flag = fcntl (s, F_GETFL, 0); + if (flag == -1) + flag = 0; + rc = fcntl (s, F_SETFL, flag | O_NONBLOCK); + errno_assert (rc != -1); + + // Bind the socket to the network interface and port. + rc = bind (s, (struct sockaddr*) &ip_address, sizeof (ip_address)); + if (rc != 0) { + close (); + return -1; + } + + // Listen for incomming connections. + rc = listen (s, tcp_connection_backlog); + if (rc != 0) { + close (); + return -1; + } + + return 0; +} + +int zs::tcp_listener_t::close () +{ + zs_assert (s != retired_fd); + int rc = ::close (s); + if (rc != 0) + return -1; + s = retired_fd; + return 0; +} + +zs::fd_t zs::tcp_listener_t::get_fd () +{ + return s; +} + +zs::fd_t zs::tcp_listener_t::accept () +{ + zs_assert (s != retired_fd); + + // Accept one incoming connection. + fd_t sock = ::accept (s, NULL, NULL); + +#if (defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD || \ + defined ZMQ_HAVE_OPENBSD || defined ZMQ_HAVE_OSX || \ + defined ZMQ_HAVE_OPENVMS) + if (sock == -1 && + (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINTR || errno == ECONNABORTED)) + return retired_fd; +#elif (defined ZMQ_HAVE_SOLARIS || defined ZMQ_HAVE_AIX) + if (sock == -1 && + (errno == EWOULDBLOCK || errno == EINTR || + errno == ECONNABORTED || errno == EPROTO)) + return retired_fd; +#elif defined ZMQ_HAVE_HPUX + if (sock == -1 && + (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINTR || errno == ECONNABORTED || errno == ENOBUFS)) + return retired_fd; +#elif defined ZMQ_HAVE_QNXNTO + if (sock == -1 && + (errno == EWOULDBLOCK || errno == EINTR || errno == ECONNABORTED)) + return retired_fd; +#endif + + errno_assert (sock != -1); + + // Set to non-blocking mode. + int flags = fcntl (s, F_GETFL, 0); + if (flags == -1) + flags = 0; + int rc = fcntl (sock, F_SETFL, flags | O_NONBLOCK); + errno_assert (rc != -1); + + // Disable Nagle's algorithm. + int flag = 1; + rc = setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, + sizeof (int)); + errno_assert (rc == 0); + +#ifdef ZS_HAVE_OPENVMS + // Disable delayed acknowledgements. + flag = 1; + rc = setsockopt (sock, IPPROTO_TCP, TCP_NODELACK, (char*) &flag, + sizeof (int)); + errno_assert (rc != SOCKET_ERROR); +#endif + + return sock; +} + +#endif diff --git a/src/tcp_listener.hpp b/src/tcp_listener.hpp new file mode 100644 index 0000000..156195b --- /dev/null +++ b/src/tcp_listener.hpp @@ -0,0 +1,65 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_TCP_LISTENER_HPP_INCLUDED__ +#define __ZS_TCP_LISTENER_HPP_INCLUDED__ + +#include "fd.hpp" + +namespace zs +{ + + // The class encapsulating simple TCP listening socket. + + class tcp_listener_t + { + public: + + tcp_listener_t (); + ~tcp_listener_t (); + + // Open TCP listining socket. Address is in + // <interface-name>:<port-number> format. Interface name may be '*' + // to bind to all the interfaces. + int open (const char *addr_); + + // Close the listening socket. + int close (); + + // Get the file descriptor to poll on to get notified about + // newly created connections. + fd_t get_fd (); + + // Accept the new connection. Returns the file descriptor of the + // newly created connection. The function may return retired_fd + // if the connection was dropped while waiting in the listen backlog. + fd_t accept (); + + private: + + // Underlying socket. + fd_t s; + + tcp_listener_t (const tcp_listener_t&); + void operator = (const tcp_listener_t&); + }; + +} + +#endif diff --git a/src/tcp_socket.cpp b/src/tcp_socket.cpp new file mode 100644 index 0000000..70fde96 --- /dev/null +++ b/src/tcp_socket.cpp @@ -0,0 +1,116 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "tcp_socket.hpp" +#include "platform.hpp" +#include "err.hpp" + +#ifdef ZS_HAVE_WINDOWS + +#include "windows.hpp" +#error + +#else + +#include <unistd.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fcntl.h> + +zs::tcp_socket_t::tcp_socket_t () : + s (retired_fd) +{ +} + +zs::tcp_socket_t::~tcp_socket_t () +{ + if (s != retired_fd) + close (); +} + +int zs::tcp_socket_t::open (fd_t fd_) +{ + assert (s == retired_fd); + s = fd_; + return 0; +} + +int zs::tcp_socket_t::close () +{ + zs_assert (s != retired_fd); + int rc = ::close (s); + if (rc != 0) + return -1; + s = retired_fd; + return 0; +} + +zs::fd_t zs::tcp_socket_t::get_fd () +{ + return s; +} + +int zs::tcp_socket_t::write (const void *data, int size) +{ + ssize_t nbytes = send (s, data, size, 0); + + // Several errors are OK. When speculative write is being done we may not + // be able to write a single byte to the socket. Also, SIGSTOP issued + // by a debugging tool can result in EINTR error. + if (nbytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINTR)) + return 0; + + // Signalise peer failure. + if (nbytes == -1 && errno == ECONNRESET) + return -1; + + errno_assert (nbytes != -1); + return (size_t) nbytes; +} + +int zs::tcp_socket_t::read (void *data, int size) +{ + ssize_t nbytes = recv (s, data, size, 0); + + // Several errors are OK. When speculative read is being done we may not + // be able to read a single byte to the socket. Also, SIGSTOP issued + // by a debugging tool can result in EINTR error. + if (nbytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINTR)) + return 0; + + // Signalise peer failure. + if (nbytes == -1 && (errno == ECONNRESET || errno == ECONNREFUSED)) + return -1; + + errno_assert (nbytes != -1); + + // Orderly shutdown by the other peer. + if (nbytes == 0) + return -1; + + return (size_t) nbytes; +} + +#endif + diff --git a/src/tcp_socket.hpp b/src/tcp_socket.hpp new file mode 100644 index 0000000..a6c61ac --- /dev/null +++ b/src/tcp_socket.hpp @@ -0,0 +1,70 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_TCP_SOCKET_HPP_INCLUDED__ +#define __ZS_TCP_SOCKET_HPP_INCLUDED__ + +#include "fd.hpp" + +namespace zs +{ + + // The class encapsulating simple TCP read/write socket. + + class tcp_socket_t + { + public: + + tcp_socket_t (); + ~tcp_socket_t (); + + // Associates a socket with a native socket descriptor. + int open (fd_t fd_); + + // Closes the underlying socket. + int close (); + + // Returns the underlying socket. Returns retired_fd when the socket + // is in the closed state. + fd_t get_fd (); + + // Writes data to the socket. Returns the number of bytes actually + // written (even zero is to be considered to be a success). In case + // of error or orderly shutdown by the other peer -1 is returned. + int write (const void *data, int size); + + // Reads data from the socket (up to 'size' bytes). Returns the number + // of bytes actually read (even zero is to be considered to be + // a success). In case of error or orderly shutdown by the other + // peer -1 is returned. + int read (void *data, int size); + + private: + + // Underlying socket. + fd_t s; + + // Disable copy construction of tcp_socket. + tcp_socket_t (const tcp_socket_t&); + void operator = (const tcp_socket_t&); + }; + +} + +#endif diff --git a/src/thread.cpp b/src/thread.cpp new file mode 100644 index 0000000..7cf54f2 --- /dev/null +++ b/src/thread.cpp @@ -0,0 +1,88 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "thread.hpp" +#include "err.hpp" +#include "platform.hpp" + +#ifdef ZS_HAVE_WINDOWS + +void zs::thread_t::start (thread_fn *tfn_, void *arg_) +{ + tfn = tfn_; + arg =arg_; + descriptor = (HANDLE) _beginthreadex (NULL, 0, + &zmq::thread_t::thread_routine, this, 0 , NULL); + win_assert (descriptor != NULL); +} + +void zs::thread_t::stop () +{ + DWORD rc = WaitForSingleObject (descriptor, INFINITE); + win_assert (rc != WAIT_FAILED); +} + +unsigned int __stdcall zs::thread_t::thread_routine (void *arg_) +{ + thread_t *self = (thread_t*) arg_; + self->tfn (self->arg); + return 0; +} + +#else + +#include <signal.h> + +void zs::thread_t::start (thread_fn *tfn_, void *arg_) +{ + tfn = tfn_; + arg =arg_; + int rc = pthread_create (&descriptor, NULL, thread_routine, this); + errno_assert (rc == 0); +} + +void zs::thread_t::stop () +{ + int rc = pthread_join (descriptor, NULL); + errno_assert (rc == 0); +} + +void *zs::thread_t::thread_routine (void *arg_) +{ +#if !defined ZMQ_HAVE_OPENVMS + // Following code will guarantee more predictable latecnies as it'll + // disallow any signal handling in the I/O thread. + sigset_t signal_set; + int rc = sigfillset (&signal_set); + errno_assert (rc == 0); + rc = pthread_sigmask (SIG_BLOCK, &signal_set, NULL); + errno_assert (rc == 0); +#endif + + thread_t *self = (thread_t*) arg_; + self->tfn (self->arg); + return NULL; +} + +#endif + + + + + diff --git a/src/thread.hpp b/src/thread.hpp new file mode 100644 index 0000000..6ee9194 --- /dev/null +++ b/src/thread.hpp @@ -0,0 +1,77 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_THREAD_HPP_INCLUDED__ +#define __ZS_THREAD_HPP_INCLUDED__ + +#include "platform.hpp" + +#ifdef ZS_HAVE_WINDOWS +#include "windows.hpp" +#else +#include <pthread.h> +#endif + +namespace zs +{ + + typedef void (thread_fn) (void*); + + // Class encapsulating OS thread. Thread initiation/termination is done + // using special functions rather than in constructor/destructor so that + // thread isn't created during object construction by accident, causing + // newly created thread to access half-initialised object. Same applies + // to the destruction process: Thread should be terminated before object + // destruction begins, otherwise it can access half-destructed object. + + class thread_t + { + public: + + inline thread_t () + { + } + + // Creates OS thread. 'tfn' is main thread function. It'll be passed + // 'arg' as an argument. + void start (thread_fn *tfn_, void *arg_); + + // Waits for thread termination. + void stop (); + + private: + +#ifdef ZS_HAVE_WINDOWS + static unsigned int __stdcall thread_routine (void *arg_); + HANDLE descriptor; +#else + static void *thread_routine (void *arg_); + pthread_t descriptor; +#endif + + thread_fn *tfn; + void *arg; + + thread_t (const thread_t&); + void operator = (const thread_t&); + }; + +} + +#endif diff --git a/src/uuid.cpp b/src/uuid.cpp new file mode 100644 index 0000000..d9883cd --- /dev/null +++ b/src/uuid.cpp @@ -0,0 +1,136 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "platform.hpp" +#include "uuid.hpp" +#include "err.hpp" + +#if defined ZS_HAVE_WINDOWS + +#include <rpcdce.h> + +zs::uuid_t::uuid_t () +{ + RPC_STATUS ret = UuidCreate (&uuid); + zs_assert (ret == RPC_S_OK); + ret = UuidToString (&uuid, &uuid_str); + zs_assert (ret == RPC_S_OK); +} + +zs::uuid_t::~uuid_t () +{ + RPC_STATUS ret = RpcStringFree(&uuid_str); + assert (ret == RPC_S_OK); +} + +const char *zs::uuid_t::to_string () +{ + return uuid_str; +} + +#elif defined ZS_HAVE_FREEBSD + +#include <stdlib.h> +#include <uuid.h> + +zs::uuid_t::uuid_t () +{ + uint32_t status; + uuid_create (&uuid, &status); + zs_assert (status == uuid_s_ok); + uuid_to_string (&uuid, &uuid_str, &status); + zs_assert (status == uuid_s_ok); +} + +zs::uuid_t::~uuid_t () +{ + free (uuid_str); +} + +const char *zs::uuid_t::to_string () +{ + return uuid_str; +} + +#elif defined ZS_HAVE_LINUX || defined ZS_HAVE_SOLARIS || defined ZS_HAVE_OSX + +#include <uuid/uuid.h> + +zs::uuid_t::uuid_t () +{ + uuid_generate (uuid); + uuid_unparse (uuid, uuid_buf); +} + +zs::uuid_t::~uuid_t () +{ +} + +const char *zs::uuid_t::to_string () +{ + return uuid_buf; +} + +#else + +#include <stdio.h> +#include <string.h> +#include <openssl/rand.h> + +zs::uuid_t::uuid_t () +{ + unsigned char rand_buf [16]; + int ret = RAND_bytes (rand_buf, sizeof rand_buf); + zs_assert (ret == 1); + + // Read in UUID fields. + memcpy (&time_low, rand_buf, sizeof time_low); + memcpy (&time_mid, rand_buf + 4, sizeof time_mid); + memcpy (&time_hi_and_version, rand_buf + 6, sizeof time_hi_and_version); + memcpy (&clock_seq_hi_and_reserved, rand_buf + 8, + sizeof clock_seq_hi_and_reserved); + memcpy (&clock_seq_low, rand_buf + 9, sizeof clock_seq_low); + memcpy (&node [0], rand_buf + 10, sizeof node); + + // Store UUID version number. + time_hi_and_version = (time_hi_and_version & 0x0fff) | 4 << 12; + + // Store UUID type. + clock_seq_hi_and_reserved = (clock_seq_hi_and_reserved & 0x3f) | 0x80; + + snprintf (uuid_buf, sizeof uuid_buf, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + time_low, + time_mid, + time_hi_and_version, + clock_seq_hi_and_reserved, + clock_seq_low, + node [0], node [1], node [2], node [3], node [4], node [5]); +} + +zs::uuid_t::~uuid_t () +{ +} + +const char *zs::uuid_t::to_string () +{ + return uuid_buf; +} + +#endif diff --git a/src/uuid.hpp b/src/uuid.hpp new file mode 100644 index 0000000..94c3e8a --- /dev/null +++ b/src/uuid.hpp @@ -0,0 +1,82 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_UUID_HPP_INCLUDED__ +#define __ZS_UUID_HPP_INCLUDED__ + +#include "platform.hpp" + +#if defined ZS_HAVE_WINDOWS +#include <rpcdce.h> +#elif defined ZS_HAVE_FREEBSD +#include <uuid.h> +#elif defined ZS_HAVE_LINUX || defined ZS_HAVE_SOLARIS || defined ZS_HAVE_OSX +#include <uuid/uuid.h> +#else +#include <stdint.h> +#endif + +namespace zs +{ + + // This class provides RFC 4122 (a Universally Unique IDentifier) + // implementation. + + class uuid_t + { + public: + + uuid_t (); + ~uuid_t (); + + // Returns a pointer to buffer containing the textual + // representation of the UUID. The caller is reponsible to + // free the allocated memory. + const char *to_string (); + + private: + + // The length of textual representation of UUID. + enum { uuid_string_len = 36 }; + +#if defined ZS_HAVE_WINDOWS + ::UUID uuid; + char *uuid_str; +#elif defined ZS_HAVE_FREEBSD + ::uuid_t uuid; + char *uuid_str; +#elif defined ZS_HAVE_LINUX || defined ZS_HAVE_SOLARIS || defined ZS_HAVE_OSX + ::uuid_t uuid; + char uuid_buf [uuid_string_len + 1]; +#else + // RFC 4122 UUID's fields + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi_and_reserved; + uint8_t clock_seq_low; + uint8_t node [6]; + + char uuid_buf [uuid_string_len + 1]; +#endif + }; + +} + +#endif diff --git a/src/windows.hpp b/src/windows.hpp new file mode 100644 index 0000000..a4a89ba --- /dev/null +++ b/src/windows.hpp @@ -0,0 +1,56 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_WINDOWS_HPP_INCLUDED__ +#define __ZS_WINDOWS_HPP_INCLUDED__ + +// The purpose of this header file is to turn on only the items actually needed +// on the windows platform. + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOUSER // No USER defines and routines. +#define NOUSER +#endif +#ifndef NOMCX // No Modem Configuration Extensions. +#define NOMCX +#endif +#ifndef NOIME // No Input Method Editor. +#define NOIME +#endif +#ifndef NOSOUND // No Sound driver routines. +#define NOSOUND +#endif + +#include <windows.h> + +// Enable winsock (not included when WIN32_LEAN_AND_MEAN is defined). +#if(_WIN32_WINNT >= 0x0400) +#include <winsock2.h> +#include <mswsock.h> +#else +#include <winsock.h> +#endif + +#include <ws2tcpip.h> +#include <ipexport.h> +#include <process.h> + +#endif diff --git a/src/wire.hpp b/src/wire.hpp new file mode 100644 index 0000000..31ebef1 --- /dev/null +++ b/src/wire.hpp @@ -0,0 +1,98 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_WIRE_HPP_INCLUDED__ +#define __ZS_WIRE_HPP_INCLUDED__ + +#include "stdint.hpp" + +namespace zs +{ + + // Helper functions to convert different integer types to/from network + // byte order. + + inline void put_uint8 (unsigned char *buffer_, uint8_t value) + { + *buffer_ = value; + } + + inline uint8_t get_uint8 (unsigned char *buffer_) + { + return *buffer_; + } + + inline void put_uint16 (unsigned char *buffer_, uint16_t value) + { + buffer_ [0] = (unsigned char) (((value) >> 8) & 0xff); + buffer_ [1] = (unsigned char) (value & 0xff); + } + + inline uint16_t get_uint16 (unsigned char *buffer_) + { + return + (((uint16_t) buffer_ [0]) << 8) | + ((uint16_t) buffer_ [1]); + } + + inline void put_uint32 (unsigned char *buffer_, uint32_t value) + { + buffer_ [0] = (unsigned char) (((value) >> 24) & 0xff); + buffer_ [1] = (unsigned char) (((value) >> 16) & 0xff); + buffer_ [2] = (unsigned char) (((value) >> 8) & 0xff); + buffer_ [3] = (unsigned char) (value & 0xff); + } + + inline uint32_t get_uint32 (unsigned char *buffer_) + { + return + (((uint32_t) buffer_ [0]) << 24) | + (((uint32_t) buffer_ [1]) << 16) | + (((uint32_t) buffer_ [2]) << 8) | + ((uint32_t) buffer_ [3]); + } + + inline void put_uint64 (unsigned char *buffer_, uint64_t value) + { + buffer_ [0] = (unsigned char) (((value) >> 56) & 0xff); + buffer_ [1] = (unsigned char) (((value) >> 48) & 0xff); + buffer_ [2] = (unsigned char) (((value) >> 40) & 0xff); + buffer_ [3] = (unsigned char) (((value) >> 32) & 0xff); + buffer_ [4] = (unsigned char) (((value) >> 24) & 0xff); + buffer_ [5] = (unsigned char) (((value) >> 16) & 0xff); + buffer_ [6] = (unsigned char) (((value) >> 8) & 0xff); + buffer_ [7] = (unsigned char) (value & 0xff); + } + + inline uint64_t get_uint64 (unsigned char *buffer_) + { + return + (((uint64_t) buffer_ [0]) << 56) | + (((uint64_t) buffer_ [1]) << 48) | + (((uint64_t) buffer_ [2]) << 40) | + (((uint64_t) buffer_ [3]) << 32) | + (((uint64_t) buffer_ [4]) << 24) | + (((uint64_t) buffer_ [5]) << 16) | + (((uint64_t) buffer_ [6]) << 8) | + ((uint64_t) buffer_ [7]); + } + +} + +#endif diff --git a/src/ypipe.hpp b/src/ypipe.hpp new file mode 100644 index 0000000..5ffd5c9 --- /dev/null +++ b/src/ypipe.hpp @@ -0,0 +1,209 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_YPIPE_HPP_INCLUDED__ +#define __ZS_YPIPE_HPP_INCLUDED__ + +#include "atomic_ptr.hpp" +#include "yqueue.hpp" +#include "platform.hpp" + +namespace zs +{ + + // Lock-free queue implementation. + // Only a single thread can read from the pipe at any specific moment. + // Only a single thread can write to the pipe at any specific moment. + // + // T is the type of the object in the queue. + // If the template parameter D is set to true, it is quaranteed that + // the pipe will die in a finite time (so that you can swich to some + // other task). If D is set to false, reading from the pipe may result + // in an infinite cycle (if the pipe is continuosly fed by new elements). + // N is granularity of the pipe (how many elements have to be inserted + // till actual memory allocation is required). + + template <typename T, bool D, int N> class ypipe_t + { + public: + + // Initialises the pipe. If 'dead' is set to true, the pipe is + // created in dead state. + inline ypipe_t (bool dead_ = true) : + stop (false) + { + // Insert terminator element into the queue. + queue.push (); + + // Let all the pointers to point to the terminator + // (unless pipe is dead, in which case c is set to NULL). + r = w = &queue.back (); + c.set (dead_ ? NULL : &queue.back ()); + } + + // Following function (write) deliberately copies uninitialised data + // when used with zs_msg. Initialising the VSM body for + // non-VSM messages won't be good for performance. + +#ifdef ZMQ_HAVE_OPENVMS +#pragma message save +#pragma message disable(UNINIT) +#endif + + // Write an item to the pipe. Don't flush it yet. + inline void write (const T &value_) + { + // Place the value to the queue, add new terminator element. + queue.back () = value_; + queue.push (); + } + +#ifdef ZMQ_HAVE_OPENVMS +#pragma message restore +#endif + + // Flush the messages into the pipe. Returns false if the reader + // thread is sleeping. In that case, caller is obliged to wake the + // reader up before using the pipe again. + inline bool flush () + { + // If there are no un-flushed items, do nothing. + if (w == &queue.back ()) + return true; + + // Try to set 'c' to 'back' + if (c.cas (w, &queue.back ()) != w) { + + // Compare-and-swap was unseccessful because 'c' is NULL. + // This means that the reader is asleep. Therefore we don't + // care about thread-safeness and update c in non-atomic + // manner. We'll return false to let the caller know + // that reader is sleeping. + c.set (&queue.back ()); + w = &queue.back (); + return false; + } + + // Reader is alive. Nothing special to do now. Just move + // the 'first un-flushed item' pointer to the end of the queue. + w = &queue.back (); + return true; + } + + // Reads an item from the pipe. Returns false if there is no value. + // available. + inline bool read (T *value_) + { + // Was the value was prefetched already? If so, return it. + if (&queue.front () != r) { + *value_ = queue.front (); + queue.pop (); + return true; + } + + // There's no prefetched value, so let us prefetch more values. + // (Note that D is a template parameter. Becaue of that one of + // the following branches will be completely optimised away + // by the compiler.) + if (D) { + + // If one prefetch was already done since last sleeping, + // don't do a new one, rather ask caller to go asleep. + if (stop) { + stop = false; + return false; + } + + // Get new items. Perform the operation in atomic fashion. + r = c.xchg (NULL); + + // If there are no elements prefetched, exit and go asleep. + // During pipe's lifetime r should never be NULL, however, + // during pipe shutdown when retrieving messages from it + // to deallocate them, this can happen. + if (&queue.front () == r || !r) { + stop = false; + return false; + } + else { + + // We want to do only a single prefetch in D scenario + // before going asleep. Thus, we set stop variable to true + // so that we can return false next time the prefetch is + // attempted. + stop = true; + } + } + else { + + // Prefetching in non-D scenario is to simply retrieve the + // pointer from c in atomic fashion. If there are no + // items to prefetch, set c to NULL (using compare-and-swap). + r = c.cas (&queue.front (), NULL); + + // If there are no elements prefetched, exit. + // During pipe's lifetime r should never be NULL, however, + // during pipe shutdown when retrieving messages from it + // to deallocate them, this can happen. + if (&queue.front () == r || !r) + return false; + } + + // There was at least one value prefetched - + // return it to the caller. + *value_ = queue.front (); + queue.pop (); + return true; + } + + protected: + + // Allocation-efficient queue to store pipe items. + // Front of the queue points to the first prefetched item, back of + // the pipe points to last un-flushed item. Front is used only by + // reader thread, while back is used only by writer thread. + yqueue_t <T, N> queue; + + // Points to the first un-flushed item. This variable is used + // exclusively by writer thread. + T *w; + + // Points to the first un-prefetched item. This variable is used + // exclusively by reader thread. + T *r; + + // The single contention point of contention between writer and + // reader thread. Points past the last flushed item. If it is NULL, + // reader is asleep. This pointer should be always accessed using + // atomic operations. + atomic_ptr_t <T> c; + + // Used only if 'D' template parameter is set to true. If true, + // prefetch was already done since last sleeping and the reader + // should go asleep instead of prefetching once more. + bool stop; + + // Disable copying of ypipe object. + ypipe_t (const ypipe_t&); + void operator = (const ypipe_t&); + }; + +} + +#endif diff --git a/src/ypollset.cpp b/src/ypollset.cpp new file mode 100644 index 0000000..0be6791 --- /dev/null +++ b/src/ypollset.cpp @@ -0,0 +1,56 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ypollset.hpp" + +zs::ypollset_t::ypollset_t () +{ +} + +void zs::ypollset_t::signal (int signal_) +{ + zs_assert (signal_ >= 0 && signal_ < wait_signal); + if (bits.btsr (signal_, wait_signal)) + sem.post (); +} + +zs::ypollset_t::signals_t zs::ypollset_t::poll () +{ + signals_t result = 0; + while (!result) { + result = bits.izte (signals_t (1) << wait_signal, 0); + if (!result) { + sem.wait (); + result = bits.xchg (0); + } + + // If btsr was really atomic, result would never be 0 at this + // point, i.e. no looping would be possible. However, to + // support even CPU architectures without CAS instruction + // we allow btsr to be composed of two independent atomic + // operation (set and reset). In such case looping can occur + // sporadically. + } + return result; +} + +zs::ypollset_t::signals_t zs::ypollset_t::check () +{ + return bits.xchg (0); +} diff --git a/src/ypollset.hpp b/src/ypollset.hpp new file mode 100644 index 0000000..7c71152 --- /dev/null +++ b/src/ypollset.hpp @@ -0,0 +1,74 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_YPOLLSET_HPP_INCLUDED__ +#define __ZS_YPOLLSET_HPP_INCLUDED__ + +#include "i_signaler.hpp" +#include "simple_semaphore.hpp" +#include "atomic_bitmap.hpp" + +namespace zs +{ + + // ypollset allows for rapid polling for up to constant number of + // different signals each produced by a different thread. The number of + // possible signals is platform-dependent. + + class ypollset_t : public i_signaler + { + public: + + typedef atomic_bitmap_t::bitmap_t signals_t; + + // Create the pollset. + ypollset_t (); + + // Send a signal to the pollset (i_singnaler implementation). + void signal (int signal_); + + // Wait for signal. Returns a set of signals in form of a bitmap. + // Signal with index 0 corresponds to value 1, index 1 to value 2, + // index 2 to value 3 etc. + signals_t poll (); + + // Same as poll, however, if there is no signal available, + // function returns zero immediately instead of waiting for a signal. + signals_t check (); + + private: + + // Wait signal is carried in the most significant bit of integer. + enum {wait_signal = sizeof (signals_t) * 8 - 1}; + + // The bits of the pollset. + atomic_bitmap_t bits; + + // Used by thread waiting for signals to sleep if there are no + // signals available. + simple_semaphore_t sem; + + // Disable copying of ypollset object. + ypollset_t (const ypollset_t&); + void operator = (const ypollset_t&); + }; + +} + +#endif diff --git a/src/yqueue.hpp b/src/yqueue.hpp new file mode 100644 index 0000000..78be17c --- /dev/null +++ b/src/yqueue.hpp @@ -0,0 +1,138 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_YQUEUE_HPP_INCLUDED__ +#define __ZS_YQUEUE_HPP_INCLUDED__ + +#include <stddef.h> + +#include "err.hpp" + +namespace zs +{ + + // yqueue is an efficient queue implementation. The main goal is + // to minimise number of allocations/deallocations needed. Thus yqueue + // allocates/deallocates elements in batches of N. + // + // yqueue allows one thread to use push/back function and another one + // to use pop/front functions. However, user must ensure that there's no + // pop on the empty queue and that both threads don't access the same + // element in unsynchronised manner. + // + // T is the type of the object in the queue + // N is granularity of the queue (how many pushes have to be done till + // actual memory allocation is required) + + template <typename T, int N> class yqueue_t + { + public: + + // Create the queue. + inline yqueue_t () + { + begin_chunk = new chunk_t; + zs_assert (begin_chunk); + begin_pos = 0; + back_chunk = NULL; + back_pos = 0; + end_chunk = begin_chunk; + end_pos = 0; + } + + // Destroy the queue. + inline ~yqueue_t () + { + while (true) { + chunk_t *o = begin_chunk; + begin_chunk = begin_chunk->next; + delete o; + if (o == end_chunk) + break; + } + } + + // Returns reference to the front element of the queue. + // If the queue is empty, behaviour is undefined. + inline T &front () + { + return begin_chunk->values [begin_pos]; + } + + // Returns reference to the back element of the queue. + // If the queue is empty, behaviour is undefined. + inline T &back () + { + return back_chunk->values [back_pos]; + } + + // Adds an element to the back end of the queue. + inline void push () + { + back_chunk = end_chunk; + back_pos = end_pos; + + if (++ end_pos != N) + return; + + end_chunk->next = new chunk_t; + zs_assert (end_chunk->next); + end_chunk = end_chunk->next; + end_pos = 0; + } + + // Removes an element from the front end of the queue. + inline void pop () + { + if (++ begin_pos == N) { + chunk_t *o = begin_chunk; + begin_chunk = begin_chunk->next; + begin_pos = 0; + delete o; + } + } + + private: + + // Individual memory chunk to hold N elements. + struct chunk_t + { + T values [N]; + chunk_t *next; + }; + + // Back position may point to invalid memory if the queue is empty, + // while begin & end positions are always valid. Begin position is + // accessed exclusively be queue reader (front/pop), while back and + // end positions are accessed exclusively by queue writer (back/push). + chunk_t *begin_chunk; + int begin_pos; + chunk_t *back_chunk; + int back_pos; + chunk_t *end_chunk; + int end_pos; + + // Disable copying of yqueue. + yqueue_t (const yqueue_t&); + void operator = (const yqueue_t&); + }; + +} + +#endif diff --git a/src/zmq_decoder.cpp b/src/zmq_decoder.cpp new file mode 100644 index 0000000..46e4752 --- /dev/null +++ b/src/zmq_decoder.cpp @@ -0,0 +1,78 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "zmq_decoder.hpp" +#include "i_session.hpp" +#include "wire.hpp" + +zs::zmq_decoder_t::zmq_decoder_t () : + destination (NULL) +{ + zs_msg_init (&in_progress); + + // At the beginning, read one byte and go to one_byte_size_ready state. + next_step (tmpbuf, 1, &zmq_decoder_t::one_byte_size_ready); +} + +zs::zmq_decoder_t::~zmq_decoder_t () +{ + zs_msg_close (&in_progress); +} + +void zs::zmq_decoder_t::set_session (i_session *destination_) +{ + destination = destination_; +} + +bool zs::zmq_decoder_t::one_byte_size_ready () +{ + // First byte of size is read. If it is 0xff read 8-byte size. + // Otherwise allocate the buffer for message data and read the + // message data into it. + if (*tmpbuf == 0xff) + next_step (tmpbuf, 8, &zmq_decoder_t::eight_byte_size_ready); + else { + zs_msg_init_size (&in_progress, *tmpbuf); + next_step (zs_msg_data (&in_progress), *tmpbuf, + &zmq_decoder_t::message_ready); + } + return true; +} + +bool zs::zmq_decoder_t::eight_byte_size_ready () +{ + // 8-byte size is read. Allocate the buffer for message body and + // read the message data into it. + size_t size = (size_t) get_uint64 (tmpbuf); + zs_msg_init_size (&in_progress, size); + next_step (zs_msg_data (&in_progress), size, &zmq_decoder_t::message_ready); + return true; +} + +bool zs::zmq_decoder_t::message_ready () +{ + // Message is completely read. Push it further and start reading + // new message. + if (!destination->write (&in_progress)) + return false; + + next_step (tmpbuf, 1, &zmq_decoder_t::one_byte_size_ready); + return true; +} + diff --git a/src/zmq_decoder.hpp b/src/zmq_decoder.hpp new file mode 100644 index 0000000..3ab8d6b --- /dev/null +++ b/src/zmq_decoder.hpp @@ -0,0 +1,57 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef __ZS_ZMQ_DECODER_HPP_INCLUDED__ +#define __ZS_ZMQ_DECODER_HPP_INCLUDED__ + +#include "../include/zs.h" + +#include "decoder.hpp" + +namespace zs +{ + // Decoder for 0MQ backend protocol. Converts data batches into messages. + + class zmq_decoder_t : public decoder_t <zmq_decoder_t> + { + public: + + zmq_decoder_t (); + ~zmq_decoder_t (); + + void set_session (struct i_session *destination_); + + private: + + bool one_byte_size_ready (); + bool eight_byte_size_ready (); + bool message_ready (); + + struct i_session *destination; + unsigned char tmpbuf [8]; + ::zs_msg in_progress; + + zmq_decoder_t (const zmq_decoder_t&); + void operator = (const zmq_decoder_t&); + }; + +} + +#endif diff --git a/src/zmq_encoder.cpp b/src/zmq_encoder.cpp new file mode 100644 index 0000000..9624b69 --- /dev/null +++ b/src/zmq_encoder.cpp @@ -0,0 +1,75 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "zmq_encoder.hpp" +#include "i_session.hpp" +#include "wire.hpp" + +zs::zmq_encoder_t::zmq_encoder_t () : + source (NULL) +{ + zs_msg_init (&in_progress); + + // Write 0 bytes to the batch and go to message_ready state. + next_step (NULL, 0, &zmq_encoder_t::message_ready, true); +} + +zs::zmq_encoder_t::~zmq_encoder_t () +{ + zs_msg_close (&in_progress); +} + +void zs::zmq_encoder_t::set_session (i_session *source_) +{ + source = source_; +} + +bool zs::zmq_encoder_t::size_ready () +{ + // Write message body into the buffer. + next_step (zs_msg_data (&in_progress), zs_msg_size (&in_progress), + &zmq_encoder_t::message_ready, false); + return true; +} + +bool zs::zmq_encoder_t::message_ready () +{ + // Read new message from the dispatcher. If there is none, return false. + // Note that new state is set only if write is successful. That way + // unsuccessful write will cause retry on the next state machine + // invocation. + if (!source->read (&in_progress)) { + return false; + } + size_t size = zs_msg_size (&in_progress); + + // For messages less than 255 bytes long, write one byte of message size. + // For longer messages write 0xff escape character followed by 8-byte + // message size. + if (size < 255) { + tmpbuf [0] = (unsigned char) size; + next_step (tmpbuf, 1, &zmq_encoder_t::size_ready, true); + } + else { + tmpbuf [0] = 0xff; + put_uint64 (tmpbuf + 1, size); + next_step (tmpbuf, 9, &zmq_encoder_t::size_ready, true); + } + return true; +} diff --git a/src/zmq_encoder.hpp b/src/zmq_encoder.hpp new file mode 100644 index 0000000..37c4aee --- /dev/null +++ b/src/zmq_encoder.hpp @@ -0,0 +1,54 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_ZMQ_ENCODER_HPP_INCLUDED__ +#define __ZS_ZMQ_ENCODER_HPP_INCLUDED__ + +#include "../include/zs.h" + +#include "encoder.hpp" + +namespace zs +{ + // Encoder for 0MQ backend protocol. Converts messages into data batches. + + class zmq_encoder_t : public encoder_t <zmq_encoder_t> + { + public: + + zmq_encoder_t (); + ~zmq_encoder_t (); + + void set_session (struct i_session *source_); + + private: + + bool size_ready (); + bool message_ready (); + + struct i_session *source; + ::zs_msg in_progress; + unsigned char tmpbuf [9]; + + zmq_encoder_t (const zmq_encoder_t&); + void operator = (const zmq_encoder_t&); + }; +} + +#endif diff --git a/src/zmq_tcp_engine.cpp b/src/zmq_tcp_engine.cpp new file mode 100644 index 0000000..0f55808 --- /dev/null +++ b/src/zmq_tcp_engine.cpp @@ -0,0 +1,185 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "zmq_tcp_engine.hpp" +#include "config.hpp" +#include "i_session.hpp" +#include "err.hpp" + +zs::zmq_tcp_engine_t::zmq_tcp_engine_t (fd_t fd_) : + poller (NULL), + session (NULL), + terminating (false), + insize (0), + inpos (0), + outsize (0), + outpos (0) +{ + // Allocate read & write buffer. + inbuf = new unsigned char [in_batch_size]; + zs_assert (inbuf); + outbuf = new unsigned char [out_batch_size]; + zs_assert (outbuf); + + // Attach the socket. + int rc = socket.open (fd_); + zs_assert (rc == 0); +} + +void zs::zmq_tcp_engine_t::attach (i_poller *poller_, i_session *session_) +{ + zs_assert (!poller); + poller = poller_; + zs_assert (!session); + session = session_; + encoder.set_session (session); + decoder.set_session (session); + + // Let session know we are here. + session->set_engine (this); + + // Register the engine with the polling thread. + handle = poller->add_fd (socket.get_fd (), this); + poller->set_pollin (handle); + poller->set_pollout (handle); + + // Flush any pending inbound messages. + in_event (); +} + +void zs::zmq_tcp_engine_t::detach () +{ + zs_assert (poller); + poller->rm_fd (handle); + poller = NULL; + zs_assert (session); + session->set_engine (NULL); + session = NULL; + encoder.set_session (NULL); + decoder.set_session (NULL); +} + +void zs::zmq_tcp_engine_t::revive () +{ + zs_assert (poller); + poller->set_pollout (handle); +} + +void zs::zmq_tcp_engine_t::schedule_terminate () +{ + terminating = true; +} + +void zs::zmq_tcp_engine_t::terminate () +{ + delete this; +} + +void zs::zmq_tcp_engine_t::shutdown () +{ + delete this; +} + +zs::zmq_tcp_engine_t::~zmq_tcp_engine_t () +{ + detach (); + delete [] outbuf; + delete [] inbuf; +} + +void zs::zmq_tcp_engine_t::in_event () +{ + // If there's no data to process in the buffer, read new data. + if (inpos == insize) { + + // Read as much data as possible to the read buffer. + insize = socket.read (inbuf, in_batch_size); + inpos = 0; + + // Check whether the peer has closed the connection. + if (insize == -1) { + insize = 0; + error (); + return; + } + } + + // Following code should be executed even if there's not a single byte in + // the buffer. There still can be a decoded messages stored in the decoder. + + // Push the data to the decoder. + int nbytes = decoder.write (inbuf + inpos, insize - inpos); + + // Adjust read position. Stop polling for input if we got stuck. + inpos += nbytes; + if (inpos < insize) + poller->reset_pollin (handle); + + // If at least one byte was processed, flush all messages the decoder + // may have produced. If engine is disconnected from session, no need + // to flush the messages. It's important that flush is called at the + // very end of in_event as it may invoke in_event itself. + if (nbytes > 0 && session) + session->flush (); +} + +void zs::zmq_tcp_engine_t::out_event () +{ + // If write buffer is empty, try to read new data from the encoder. + if (outpos == outsize) { + + outsize = encoder.read (outbuf, out_batch_size); + outpos = 0; + + // If there are no more pipes, engine can be deallocated. + if (terminating) { + terminate (); + return; + } + + // If there is no data to send, stop polling for output. + if (outsize == 0) + poller->reset_pollout (handle); + } + + // If there are any data to write in write buffer, write as much as + // possible to the socket. + if (outpos < outsize) { + int nbytes = socket.write (outbuf + outpos, outsize - outpos); + + // Handle problems with the connection. + if (nbytes == -1) { + error (); + return; + } + + outpos += nbytes; + } +} + +void zs::zmq_tcp_engine_t::timer_event () +{ + zs_assert (false); +} + +void zs::zmq_tcp_engine_t::error () +{ + zs_assert (false); +} + diff --git a/src/zmq_tcp_engine.hpp b/src/zmq_tcp_engine.hpp new file mode 100644 index 0000000..fb1413c --- /dev/null +++ b/src/zmq_tcp_engine.hpp @@ -0,0 +1,92 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ZS_ZMQ_TCP_ENGINE_HPP_INCLUDED__ +#define __ZS_ZMQ_TCP_ENGINE_HPP_INCLUDED__ + +#include "i_engine.hpp" +#include "i_poller.hpp" +#include "i_poll_events.hpp" +#include "fd.hpp" +#include "tcp_socket.hpp" +#include "zmq_encoder.hpp" +#include "zmq_decoder.hpp" + +namespace zs +{ + + class zmq_tcp_engine_t : public i_engine, public i_poll_events + { + public: + + zmq_tcp_engine_t (fd_t fd_); + + // i_engine implementation. + void attach (struct i_poller *poller_, struct i_session *session_); + void detach (); + void revive (); + void schedule_terminate (); + void terminate (); + void shutdown (); + + // i_poll_events implementation. + void in_event (); + void out_event (); + void timer_event (); + + private: + + void error (); + + // Clean-up. + ~zmq_tcp_engine_t (); + + // The underlying TCP socket. + tcp_socket_t socket; + + // Handle associated with the socket. + handle_t handle; + + // I/O thread that the engine runs in. + i_poller *poller; + + // Reference to the associated session object. + i_session *session; + + // If true, engine should terminate itself as soon as possible. + bool terminating; + + unsigned char *inbuf; + int insize; + int inpos; + + unsigned char *outbuf; + int outsize; + int outpos; + + zmq_encoder_t encoder; + zmq_decoder_t decoder; + + zmq_tcp_engine_t (const zmq_tcp_engine_t&); + void operator = (const zmq_tcp_engine_t&); + }; + +} + +#endif diff --git a/src/zs.cpp b/src/zs.cpp new file mode 100644 index 0000000..ca05db3 --- /dev/null +++ b/src/zs.cpp @@ -0,0 +1,222 @@ +/* + Copyright (c) 2007-2009 FastMQ Inc. + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU 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 + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../include/zs.h" + +#include <errno.h> +#include <stdlib.h> +#include <new> + +#include "i_api.hpp" +#include "err.hpp" +#include "dispatcher.hpp" +#include "msg.hpp" + +int zs_msg_init (zs_msg *msg_) +{ + msg_->content = (zs_msg_content*) ZS_VSM; + msg_->vsm_size = 0; + return 0; +} + +int zs_msg_init_size (zs_msg *msg_, size_t size_) +{ + if (size_ <= ZS_MAX_VSM_SIZE) { + msg_->content = (zs_msg_content*) ZS_VSM; + msg_->vsm_size = (uint16_t) size_; + } + else { + msg_->content = (zs_msg_content*) malloc (sizeof (zs_msg_content) + + size_); + if (!msg_->content) { + errno = ENOMEM; + return -1; + } + msg_->shared = 0; + + msg_->content->data = (void*) (msg_->content + 1); + msg_->content->size = size_; + msg_->content->ffn = NULL; + new (&msg_->content->refcnt) zs::atomic_counter_t (); + } + return 0; +} + +int zs_msg_init_data (zs_msg *msg_, void *data_, size_t size_, zs_free_fn *ffn_) +{ + msg_->shared = 0; + msg_->content = (zs_msg_content*) malloc (sizeof (zs_msg_content)); + zs_assert (msg_->content); + msg_->content->data = data_; + msg_->content->size = size_; + msg_->content->ffn = ffn_; + new (&msg_->content->refcnt) zs::atomic_counter_t (); + return 0; +} + +int zs_msg_close (zs_msg *msg_) +{ + // For VSMs and delimiters there are no resources to free + if (msg_->content == (zs_msg_content*) ZS_DELIMITER || + msg_->content == (zs_msg_content*) ZS_VSM || + msg_->content == (zs_msg_content*) ZS_GAP) + return 0; + + // If the content is not shared, or if it is shared and the reference + // count has dropped to zero, deallocate it. + if (!msg_->shared || !msg_->content->refcnt.sub (1)) { + + // We used "placement new" operator to initialize the reference + // counter so we call its destructor now. + msg_->content->refcnt.~atomic_counter_t (); + + if (msg_->content->ffn) + msg_->content->ffn (msg_->content->data); + free (msg_->content); + } + + return 0; +} + +int zs_msg_move (zs_msg *dest_, zs_msg *src_) +{ + zs_msg_close (dest_); + *dest_ = *src_; + zs_msg_init (src_); + return 0; +} + +int zs_msg_copy (zs_msg *dest_, zs_msg *src_) +{ + zs_msg_close (dest_); + + // VSMs and delimiters require no special handling. + if (src_->content != + (zs_msg_content*) ZS_DELIMITER && + src_->content != (zs_msg_content*) ZS_VSM && + src_->content != (zs_msg_content*) ZS_GAP) { + + // One reference is added to shared messages. Non-shared messages + // are turned into shared messages and reference count is set to 2. + if (src_->shared) + src_->content->refcnt.add (1); + else { + src_->shared = true; + src_->content->refcnt.set (2); + } + } + + *dest_ = *src_; + return 0; +} + +void *zs_msg_data (zs_msg *msg_) +{ + if (msg_->content == (zs_msg_content*) ZS_VSM) + return msg_->vsm_data; + if (msg_->content == + (zs_msg_content*) ZS_DELIMITER || + msg_->content == (zs_msg_content*) ZS_GAP) + return NULL; + return msg_->content->data; +} + +size_t zs_msg_size (zs_msg *msg_) +{ + if (msg_->content == (zs_msg_content*) ZS_VSM) + return msg_->vsm_size; + if (msg_->content == + (zs_msg_content*) ZS_DELIMITER || + msg_->content == (zs_msg_content*) ZS_GAP) + return 0; + return msg_->content->size; +} + +int zs_msg_type (zs_msg *msg_) +{ + // If it's a genuine message, return 0. + if (msg_->content >= (zs_msg_content*) ZS_VSM) + return 0; + + // Trick the compiler to believe that content is an integer. + unsigned char *offset = 0; + return (((const unsigned char*) msg_->content) - offset); +} + +void *zs_init (int app_threads_, int io_threads_) +{ + // There should be at least a single thread managed by the dispatcher. + if (app_threads_ < 0 || io_threads_ < 0 || + app_threads_ + io_threads_ == 0) { + errno = EINVAL; + return NULL; + } + + zs::dispatcher_t *dispatcher = + new zs::dispatcher_t (app_threads_, io_threads_); + zs_assert (dispatcher); + return (void*) dispatcher; +} + +int zs_term (void *context_) +{ + ((zs::dispatcher_t*) context_)->shutdown (); + return 0; +} + +void *zs_socket (void *context_, int type_) +{ + return (void*) (((zs::dispatcher_t*) context_)->create_socket (type_)); +} + +int zs_close (void *s_) +{ + ((zs::i_api*) s_)->close (); + return 0; +} + +int zs_bind (void *s_, const char *addr_, zs_opts *opts_) +{ + return (((zs::i_api*) s_)->bind (addr_, opts_)); +} + +int zs_connect (void *s_, const char *addr_, zs_opts *opts_) +{ + return (((zs::i_api*) s_)->connect (addr_, opts_)); +} + +int zs_subscribe (void *s_, const char *criteria_) +{ + return (((zs::i_api*) s_)->subscribe (criteria_)); +} + +int zs_send (void *s_, zs_msg *msg_, int flags_) +{ + return (((zs::i_api*) s_)->send (msg_, flags_)); +} + +int zs_flush (void *s_) +{ + return (((zs::i_api*) s_)->flush ()); +} + +int zs_recv (void *s_, zs_msg *msg_, int flags_) +{ + return (((zs::i_api*) s_)->recv (msg_, flags_)); +} |