couple of bugs in shutdown mechanism fixed

This commit is contained in:
Martin Sustrik 2009-08-20 11:32:23 +02:00
parent 131f2e3096
commit a801b6d8b3
16 changed files with 185 additions and 74 deletions

View File

@ -37,11 +37,11 @@ namespace zmq
stop, stop,
plug, plug,
own, own,
attach,
bind, bind,
term_req, term_req,
term, term,
term_ack term_ack
} type; } type;
union { union {
@ -57,9 +57,14 @@ namespace zmq
// Sent to socket to let it know about the newly created object. // Sent to socket to let it know about the newly created object.
struct { struct {
class object_t *object; class owned_t *object;
} own; } own;
// Attach the engine to the session.
struct {
class zmq_engine_t *engine;
} attach;
// Sent between objects to establish pipe(s) between them. // Sent between objects to establish pipe(s) between them.
struct { struct {
} bind; } bind;
@ -67,7 +72,7 @@ namespace zmq
// Sent by I/O object ot the socket to request the shutdown of // Sent by I/O object ot the socket to request the shutdown of
// the I/O object. // the I/O object.
struct { struct {
class object_t *object; class owned_t *object;
} term_req; } term_req;
// Sent by socket to I/O object to start its shutdown. // Sent by socket to I/O object to start its shutdown.

View File

@ -22,6 +22,10 @@
#include "err.hpp" #include "err.hpp"
#include "io_thread.hpp" #include "io_thread.hpp"
#include "simple_semaphore.hpp" #include "simple_semaphore.hpp"
#include "owned.hpp"
#include "session.hpp"
#include "socket_base.hpp"
#include "zmq_engine.hpp" // TODO: remove this line
zmq::object_t::object_t (dispatcher_t *dispatcher_, int thread_slot_) : zmq::object_t::object_t (dispatcher_t *dispatcher_, int thread_slot_) :
dispatcher (dispatcher_), dispatcher (dispatcher_),
@ -65,6 +69,10 @@ void zmq::object_t::process_command (command_t &cmd_)
process_own (cmd_.args.own.object); process_own (cmd_.args.own.object);
return; return;
case command_t::attach:
process_attach (cmd_.args.attach.engine);
return;
case command_t::bind: case command_t::bind:
process_bind (); process_bind ();
return; return;
@ -101,15 +109,18 @@ void zmq::object_t::send_stop ()
dispatcher->write (thread_slot, thread_slot, cmd); dispatcher->write (thread_slot, thread_slot, cmd);
} }
void zmq::object_t::send_plug (object_t *destination_) void zmq::object_t::send_plug (owned_t *destination_)
{ {
// Let the object know that it cannot shut down till it gets this command.
destination_->inc_seqnum ();
command_t cmd; command_t cmd;
cmd.destination = destination_; cmd.destination = destination_;
cmd.type = command_t::plug; cmd.type = command_t::plug;
send_command (cmd); send_command (cmd);
} }
void zmq::object_t::send_own (object_t *destination_, object_t *object_) void zmq::object_t::send_own (socket_base_t *destination_, owned_t *object_)
{ {
command_t cmd; command_t cmd;
cmd.destination = destination_; cmd.destination = destination_;
@ -118,6 +129,18 @@ void zmq::object_t::send_own (object_t *destination_, object_t *object_)
send_command (cmd); send_command (cmd);
} }
void zmq::object_t::send_attach (session_t *destination_, zmq_engine_t *engine_)
{
// Let the object know that it cannot shut down till it gets this command.
destination_->inc_seqnum ();
command_t cmd;
cmd.destination = destination_;
cmd.type = command_t::attach;
cmd.args.attach.engine = engine_;
send_command (cmd);
}
void zmq::object_t::send_bind (object_t *destination_) void zmq::object_t::send_bind (object_t *destination_)
{ {
command_t cmd; command_t cmd;
@ -126,7 +149,8 @@ void zmq::object_t::send_bind (object_t *destination_)
send_command (cmd); send_command (cmd);
} }
void zmq::object_t::send_term_req (object_t *destination_, object_t *object_) void zmq::object_t::send_term_req (socket_base_t *destination_,
owned_t *object_)
{ {
command_t cmd; command_t cmd;
cmd.destination = destination_; cmd.destination = destination_;
@ -135,7 +159,7 @@ void zmq::object_t::send_term_req (object_t *destination_, object_t *object_)
send_command (cmd); send_command (cmd);
} }
void zmq::object_t::send_term (object_t *destination_) void zmq::object_t::send_term (owned_t *destination_)
{ {
command_t cmd; command_t cmd;
cmd.destination = destination_; cmd.destination = destination_;
@ -143,7 +167,7 @@ void zmq::object_t::send_term (object_t *destination_)
send_command (cmd); send_command (cmd);
} }
void zmq::object_t::send_term_ack (object_t *destination_) void zmq::object_t::send_term_ack (socket_base_t *destination_)
{ {
command_t cmd; command_t cmd;
cmd.destination = destination_; cmd.destination = destination_;
@ -161,7 +185,12 @@ void zmq::object_t::process_plug ()
zmq_assert (false); zmq_assert (false);
} }
void zmq::object_t::process_own (object_t *object_) void zmq::object_t::process_own (owned_t *object_)
{
zmq_assert (false);
}
void zmq::object_t::process_attach (zmq_engine_t *engine_)
{ {
zmq_assert (false); zmq_assert (false);
} }
@ -171,7 +200,7 @@ void zmq::object_t::process_bind ()
zmq_assert (false); zmq_assert (false);
} }
void zmq::object_t::process_term_req (object_t *object_) void zmq::object_t::process_term_req (owned_t *object_)
{ {
zmq_assert (false); zmq_assert (false);
} }

View File

@ -49,20 +49,25 @@ namespace zmq
// Derived object can use these functions to send commands // Derived object can use these functions to send commands
// to other objects. // to other objects.
void send_stop (); void send_stop ();
void send_plug (object_t *destination_); void send_plug (class owned_t *destination_);
void send_own (object_t *destination_, object_t *object_); void send_own (class socket_base_t *destination_,
class owned_t *object_);
void send_attach (class session_t *destination_,
class zmq_engine_t *engine_);
void send_bind (object_t *destination_); void send_bind (object_t *destination_);
void send_term_req (object_t *destination_, object_t *object_); void send_term_req (class socket_base_t *destination_,
void send_term (object_t *destination_); class owned_t *object_);
void send_term_ack (object_t *destination_); void send_term (class owned_t *destination_);
void send_term_ack (class socket_base_t *destination_);
// These handlers can be overloaded by the derived objects. They are // These handlers can be overloaded by the derived objects. They are
// called when command arrives from another thread. // called when command arrives from another thread.
virtual void process_stop (); virtual void process_stop ();
virtual void process_plug (); virtual void process_plug ();
virtual void process_own (object_t *object_); virtual void process_own (class owned_t *object_);
virtual void process_attach (class zmq_engine_t *engine_);
virtual void process_bind (); virtual void process_bind ();
virtual void process_term_req (object_t *object_); virtual void process_term_req (class owned_t *object_);
virtual void process_term (); virtual void process_term ();
virtual void process_term_ack (); virtual void process_term_ack ();

View File

@ -20,11 +20,12 @@
#include "owned.hpp" #include "owned.hpp"
#include "err.hpp" #include "err.hpp"
zmq::owned_t::owned_t (object_t *parent_, object_t *owner_) : zmq::owned_t::owned_t (object_t *parent_, socket_base_t *owner_) :
object_t (parent_), object_t (parent_),
owner (owner_), owner (owner_),
plugged_in (false), sent_seqnum (0),
terminated (false) processed_seqnum (0),
shutting_down (false)
{ {
} }
@ -32,21 +33,18 @@ zmq::owned_t::~owned_t ()
{ {
} }
void zmq::owned_t::inc_seqnum ()
{
// NB: This function may be called from a different thread!
sent_seqnum.add (1);
}
void zmq::owned_t::process_plug () void zmq::owned_t::process_plug ()
{ {
zmq_assert (!plugged_in); // Keep track of how many commands were processed so far.
processed_seqnum++;
// If termination of the object was already requested, destroy it and finalise_command ();
// send the termination acknowledgement.
if (terminated) {
send_term_ack (owner);
delete this;
return;
}
// Notify the generic termination mechanism (io_object_t) that the object
// is already plugged in.
plugged_in = true;
} }
void zmq::owned_t::term () void zmq::owned_t::term ()
@ -56,19 +54,20 @@ void zmq::owned_t::term ()
void zmq::owned_t::process_term () void zmq::owned_t::process_term ()
{ {
zmq_assert (!terminated); zmq_assert (!shutting_down);
shutting_down = true;
// If termination request has occured even before the object was plugged in finalise_command ();
// wait till plugging in happens, then acknowledge the termination. }
if (!plugged_in) {
terminated = true; void zmq::owned_t::finalise_command ()
return; {
} // If termination request was already received and there are no more
// commands to wait for, terminate the object.
// Otherwise, destroy the object and acknowledge the termination if (shutting_down && processed_seqnum == sent_seqnum.get ()) {
// straight away. send_term_ack (owner);
send_term_ack (owner); process_unplug ();
process_unplug (); delete this;
delete this; }
} }

View File

@ -20,7 +20,9 @@
#ifndef __ZMQ_OWNED_HPP_INCLUDED__ #ifndef __ZMQ_OWNED_HPP_INCLUDED__
#define __ZMQ_OWNED_HPP_INCLUDED__ #define __ZMQ_OWNED_HPP_INCLUDED__
#include "object.hpp" #include "socket_base.hpp"
#include "atomic_counter.hpp"
#include "stdint.hpp"
namespace zmq namespace zmq
{ {
@ -34,7 +36,12 @@ namespace zmq
// The object will live in parent's thread, however, its lifetime // The object will live in parent's thread, however, its lifetime
// will be managed by its owner socket. // will be managed by its owner socket.
owned_t (object_t *parent_, object_t *owner_); owned_t (object_t *parent_, socket_base_t *owner_);
// When another owned object wants to send command to this object
// it calls this function to let it know it should not shut down
// before the command is delivered.
void inc_seqnum ();
protected: protected:
@ -57,21 +64,27 @@ namespace zmq
// classes to ensure sane cleanup. // classes to ensure sane cleanup.
virtual void process_unplug () = 0; virtual void process_unplug () = 0;
// Socket owning this object. It is responsible for destroying // Socket owning this object. When the socket is being closed it's
// it when it's being closed. // responsible for shutting down this object.
object_t *owner; socket_base_t *owner;
private: private:
// Handlers for incoming commands. // Handlers for incoming commands.
void process_term (); void process_term ();
// Set to true when object is plugged in. // Generic command handler (to be called from all command handlers
bool plugged_in; // once the processing is done).
void finalise_command ();
// Set to true when object was terminated before it was plugged in. // Sequence number of the last command sent to this object.
// In such case destruction is delayed till 'plug' command arrives. atomic_counter_t sent_seqnum;
bool terminated;
// Sequence number of the last command processed by this object.
uint64_t processed_seqnum;
// If true, the object is already shutting down.
bool shutting_down;
owned_t (const owned_t&); owned_t (const owned_t&);
void operator = (const owned_t&); void operator = (const owned_t&);

View File

@ -21,7 +21,7 @@
#include "zmq_engine.hpp" #include "zmq_engine.hpp"
#include "err.hpp" #include "err.hpp"
zmq::session_t::session_t (object_t *parent_, object_t *owner_, zmq::session_t::session_t (object_t *parent_, socket_base_t *owner_,
zmq_engine_t *engine_) : zmq_engine_t *engine_) :
owned_t (parent_, owner_), owned_t (parent_, owner_),
engine (engine_) engine (engine_)
@ -48,11 +48,14 @@ void zmq::session_t::flush ()
void zmq::session_t::process_plug () void zmq::session_t::process_plug ()
{ {
zmq_assert (engine);
engine->plug (this); engine->plug (this);
owned_t::process_plug (); owned_t::process_plug ();
} }
void zmq::session_t::process_unplug () void zmq::session_t::process_unplug ()
{ {
zmq_assert (engine);
engine->unplug (); engine->unplug ();
delete engine;
} }

View File

@ -30,7 +30,7 @@ namespace zmq
{ {
public: public:
session_t (object_t *parent_, object_t *owner_, session_t (object_t *parent_, socket_base_t *owner_,
class zmq_engine_t *engine_); class zmq_engine_t *engine_);
private: private:

View File

@ -27,7 +27,9 @@
#include "zmq_listener.hpp" #include "zmq_listener.hpp"
#include "zmq_connecter.hpp" #include "zmq_connecter.hpp"
#include "io_thread.hpp" #include "io_thread.hpp"
#include "session.hpp"
#include "config.hpp" #include "config.hpp"
#include "owned.hpp"
zmq::socket_base_t::socket_base_t (app_thread_t *parent_) : zmq::socket_base_t::socket_base_t (app_thread_t *parent_) :
object_t (parent_), object_t (parent_),
@ -60,6 +62,9 @@ zmq::socket_base_t::~socket_base_t ()
while (pending_term_acks) while (pending_term_acks)
app_thread->process_commands (true); app_thread->process_commands (true);
} }
// Check whether there are no session leaks.
zmq_assert (sessions.empty ());
} }
int zmq::socket_base_t::setsockopt (int option_, void *optval_, int zmq::socket_base_t::setsockopt (int option_, void *optval_,
@ -169,12 +174,43 @@ int zmq::socket_base_t::close ()
return 0; return 0;
} }
void zmq::socket_base_t::process_own (object_t *object_) void zmq::socket_base_t::register_session (const char *name_,
session_t *session_)
{
sessions_sync.lock ();
bool inserted = sessions.insert (std::make_pair (name_, session_)).second;
zmq_assert (inserted);
sessions_sync.unlock ();
}
void zmq::socket_base_t::unregister_session (const char *name_)
{
sessions_sync.lock ();
sessions_t::iterator it = sessions.find (name_);
zmq_assert (it != sessions.end ());
sessions.erase (it);
sessions_sync.unlock ();
}
zmq::session_t *zmq::socket_base_t::get_session (const char *name_)
{
sessions_sync.lock ();
sessions_t::iterator it = sessions.find (name_);
session_t *session = NULL;
if (it != sessions.end ()) {
session = it->second;
session->inc_seqnum ();
}
sessions_sync.unlock ();
return session;
}
void zmq::socket_base_t::process_own (owned_t *object_)
{ {
io_objects.insert (object_); io_objects.insert (object_);
} }
void zmq::socket_base_t::process_term_req (object_t *object_) void zmq::socket_base_t::process_term_req (owned_t *object_)
{ {
// If I/O object is well and alive ask it to terminate. // If I/O object is well and alive ask it to terminate.
io_objects_t::iterator it = std::find (io_objects.begin (), io_objects_t::iterator it = std::find (io_objects.begin (),

View File

@ -21,9 +21,11 @@
#define __ZMQ_SOCKET_BASE_HPP_INCLUDED__ #define __ZMQ_SOCKET_BASE_HPP_INCLUDED__
#include <set> #include <set>
#include <map>
#include <string> #include <string>
#include "object.hpp" #include "object.hpp"
#include "mutex.hpp"
#include "options.hpp" #include "options.hpp"
#include "stdint.hpp" #include "stdint.hpp"
@ -46,16 +48,26 @@ namespace zmq
virtual int recv (struct zmq_msg *msg_, int flags_); virtual int recv (struct zmq_msg *msg_, int flags_);
virtual int close (); virtual int close ();
// Functions that owned objects use to manipulate socket's list
// of existing sessions.
// Note that this functionality cannot be implemented via inter-thread
// commands as it is unacceptable to wait for the completion of the
// action till user application yields control of the application
// thread to 0MQ.
void register_session (const char *name_, class session_t *session_);
void unregister_session (const char *name_);
class session_t *get_session (const char *name_);
private: private:
// Handlers for incoming commands. // Handlers for incoming commands.
void process_own (object_t *object_); void process_own (class owned_t *object_);
void process_term_req (object_t *object_); void process_term_req (class owned_t *object_);
void process_term_ack (); void process_term_ack ();
// List of all I/O objects owned by this socket. The socket is // List of all I/O objects owned by this socket. The socket is
// responsible for deallocating them before it quits. // responsible for deallocating them before it quits.
typedef std::set <object_t*> io_objects_t; typedef std::set <class owned_t*> io_objects_t;
io_objects_t io_objects; io_objects_t io_objects;
// Number of I/O objects that were already asked to terminate // Number of I/O objects that were already asked to terminate
@ -68,6 +80,14 @@ namespace zmq
// Socket options. // Socket options.
options_t options; options_t options;
// List of existing sessions. This list is never referenced from within
// the socket, instead it is used by I/O objects owned by the session.
// As those objects can live in different threads, the access is
// synchronised using 'sessions_sync' mutex.
typedef std::map <std::string, session_t*> sessions_t;
sessions_t sessions;
mutex_t sessions_sync;
socket_base_t (const socket_base_t&); socket_base_t (const socket_base_t&);
void operator = (const socket_base_t&); void operator = (const socket_base_t&);
}; };

View File

@ -22,8 +22,8 @@
#include "io_thread.hpp" #include "io_thread.hpp"
#include "err.hpp" #include "err.hpp"
zmq::zmq_connecter_t::zmq_connecter_t (io_thread_t *parent_, object_t *owner_, zmq::zmq_connecter_t::zmq_connecter_t (io_thread_t *parent_,
const options_t &options_) : socket_base_t *owner_, const options_t &options_) :
owned_t (parent_, owner_), owned_t (parent_, owner_),
io_object_t (parent_), io_object_t (parent_),
handle_valid (false), handle_valid (false),

View File

@ -33,7 +33,7 @@ namespace zmq
{ {
public: public:
zmq_connecter_t (class io_thread_t *parent_, object_t *owner_, zmq_connecter_t (class io_thread_t *parent_, socket_base_t *owner_,
const options_t &options_); const options_t &options_);
// Set IP address to connect to. // Set IP address to connect to.

View File

@ -73,7 +73,6 @@ void zmq::zmq_engine_t::in_event ()
// Read as much data as possible to the read buffer. // Read as much data as possible to the read buffer.
insize = tcp_socket.read (inbuf, in_batch_size); insize = tcp_socket.read (inbuf, in_batch_size);
printf ("%d bytes read\n", (int) insize);
inpos = 0; inpos = 0;
// Check whether the peer has closed the connection. // Check whether the peer has closed the connection.
@ -132,5 +131,5 @@ void zmq::zmq_engine_t::out_event ()
void zmq::zmq_engine_t::error () void zmq::zmq_engine_t::error ()
{ {
zmq_assert (false); // zmq_assert (false);
} }

View File

@ -22,8 +22,8 @@
#include "session.hpp" #include "session.hpp"
#include "err.hpp" #include "err.hpp"
zmq::zmq_init_t::zmq_init_t (io_thread_t *parent_, object_t *owner_, fd_t fd_, zmq::zmq_init_t::zmq_init_t (io_thread_t *parent_, socket_base_t *owner_,
bool connected_, const options_t &options_) : fd_t fd_, bool connected_, const options_t &options_) :
owned_t (parent_, owner_), owned_t (parent_, owner_),
connected (connected_), connected (connected_),
options (options_) options (options_)
@ -81,13 +81,15 @@ void zmq::zmq_init_t::flush ()
void zmq::zmq_init_t::process_plug () void zmq::zmq_init_t::process_plug ()
{ {
zmq_assert (engine);
engine->plug (this); engine->plug (this);
owned_t::process_plug (); owned_t::process_plug ();
} }
void zmq::zmq_init_t::process_unplug () void zmq::zmq_init_t::process_unplug ()
{ {
engine->unplug (); if (engine)
engine->unplug ();
} }
void zmq::zmq_init_t::create_session () void zmq::zmq_init_t::create_session ()

View File

@ -44,7 +44,7 @@ namespace zmq
// Set 'connected' to true if the connection was created by 'connect' // Set 'connected' to true if the connection was created by 'connect'
// function. If it was accepted from a listening socket, set it to // function. If it was accepted from a listening socket, set it to
// false. // false.
zmq_init_t (class io_thread_t *parent_, object_t *owner_, fd_t fd_, zmq_init_t (class io_thread_t *parent_, socket_base_t *owner_, fd_t fd_,
bool connected_, const options_t &options); bool connected_, const options_t &options);
~zmq_init_t (); ~zmq_init_t ();

View File

@ -22,8 +22,8 @@
#include "io_thread.hpp" #include "io_thread.hpp"
#include "err.hpp" #include "err.hpp"
zmq::zmq_listener_t::zmq_listener_t (io_thread_t *parent_, object_t *owner_, zmq::zmq_listener_t::zmq_listener_t (io_thread_t *parent_,
const options_t &options_) : socket_base_t *owner_, const options_t &options_) :
owned_t (parent_, owner_), owned_t (parent_, owner_),
io_object_t (parent_), io_object_t (parent_),
options (options_) options (options_)

View File

@ -33,7 +33,7 @@ namespace zmq
{ {
public: public:
zmq_listener_t (class io_thread_t *parent_, object_t *owner_, zmq_listener_t (class io_thread_t *parent_, socket_base_t *owner_,
const options_t &options_); const options_t &options_);
// Set IP address to listen on. // Set IP address to listen on.