diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 9678158..353bbdf 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -768,6 +768,7 @@ WARN_LOGFILE = INPUT = ../src/client/pv \ ../src/client/pva \ ../src/server/pv \ + ../src/server/pva \ ../src/rpcClient/pv \ ../src/rpcService/pv \ ../src/utils/pv \ diff --git a/documentation/examples.h b/documentation/examples.h index 8d8e094..4a91e82 100644 --- a/documentation/examples.h +++ b/documentation/examples.h @@ -55,3 +55,9 @@ This example demonstrates a client which sets up a persistent Monitor operation. @include monitorme.cpp */ +/** @page examples_mailbox Server Mailbox Example +This example creates a server with one or more pvas::SharedPV::buildMailbox() +instances. + +@include mailbox.cpp +*/ diff --git a/documentation/mainpage.h b/documentation/mainpage.h index ab899d2..4e1362f 100644 --- a/documentation/mainpage.h +++ b/documentation/mainpage.h @@ -8,6 +8,7 @@ - @ref pvarelease_notes - API documentation - @ref pvac page + - @ref pvas page - @ref providers page - @ref pvtools @@ -21,7 +22,7 @@ - @ref examples_getme - @ref examples_putme - @ref examples_monitorme - + - @ref examples_mailbox */ #endif /* MAINPAGE_H */ diff --git a/src/factory/ChannelAccessFactory.cpp b/src/factory/ChannelAccessFactory.cpp index a5a1976..c816646 100644 --- a/src/factory/ChannelAccessFactory.cpp +++ b/src/factory/ChannelAccessFactory.cpp @@ -29,6 +29,10 @@ using namespace epics::pvData; using std::string; +namespace pvas { +void registerRefTrackServer(); +} + namespace epics { namespace pvAccess { @@ -220,6 +224,7 @@ void providerRegInit(void*) registerRefCounter("ChannelRequest (ABC)", &ChannelRequest::num_instances); registerRefCounter("ResponseHandler (ABC)", &ResponseHandler::num_instances); registerRefCounter("MonitorFIFO", &MonitorFIFO::num_instances); + pvas::registerRefTrackServer(); } ChannelProviderRegistry::shared_pointer ChannelProviderRegistry::clients() diff --git a/src/server/Makefile b/src/server/Makefile index 3e94a4f..7b0195e 100644 --- a/src/server/Makefile +++ b/src/server/Makefile @@ -4,6 +4,7 @@ SRC_DIRS += $(PVACCESS_SRC)/server INC += pv/serverContext.h INC += pv/beaconServerStatusProvider.h +INC += pva/server.h pvAccess_SRCS += responseHandlers.cpp pvAccess_SRCS += serverContext.cpp @@ -11,3 +12,4 @@ pvAccess_SRCS += serverChannelImpl.cpp pvAccess_SRCS += baseChannelRequester.cpp pvAccess_SRCS += beaconEmitter.cpp pvAccess_SRCS += beaconServerStatusProvider.cpp +pvAccess_SRCS += server.cpp diff --git a/src/server/pva/server.h b/src/server/pva/server.h new file mode 100644 index 0000000..89c54cc --- /dev/null +++ b/src/server/pva/server.h @@ -0,0 +1,222 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ +#ifndef PVA_SERVER_H +#define PVA_SERVER_H + +#include +#include +#include + +#include +#include +#include + +namespace epics{namespace pvAccess{ +class ChannelProvider; +class Channel; +class ChannelRequester; +}} // epics::pvAccess + +//! See @ref pvas API +namespace pvas { + +/** @addtogroup pvas Server API + * + * PVA Server Providers, for use with a PVA epics::pvAccess::ServerContext + * + * These are implementations of epics::pvAccess::ChannelProvider which manage "PVs", + * which are sources of epics::pvAccess::Channel instances. Typically SharedPV . + * + * Two containers are provided StaticProvider, and for some special cases DynamicProvider. + * It is recommended to use StaticProvider where possible, with DynamicProvider for exception cases. + * + * A StaticProvider maintains an internal lookup table of StaticProvider::ChannelBuilder (aka. SharedPV). + * This table is manipulated by StaticProvider::add() and StaticProvider::remove(), which can + * be called at any time. + * + * A DynamicProvider does not maintain an internal lookup table. Instead it provides + * the DynamicProvider::Handler interface, through which remote search and connection + * requests are delivered. + * + * See @ref examples_mailbox for a working example. + * + @code + namespace pva = epics::pvAccess; + pvas::SharedPV::shared_pointer pv(pvas::SharedPV::buildMailbox()); + pvas::StaticProvider sprov("arbitrary"); + pva::ServerContext::shared_pointer server( + pva::ServerContext::create( + pva::ServerContext::Config() .provider(sprov.provider()) )); + sprov->add("pv:name", pv); + @endcode + * + * @section pvas_sharedptr Server API shared_ptr Ownership + * + * shared_ptr<> relationships internal to server API classes. + * Solid red lines are shared_ptr<>. + * Dashed red lines are shared_ptr<> which may exist safely in user code. + * Rectangles are public API classes. Circles are internal classes. + * "ChannelProvider" is an arbitrary ChannelProvider, possibly StaticProvider or DynamicProvider. + * + @dot "Internal shared_ptr<> relationships. + digraph sspv { + SharedPV [shape="box"]; + SharedPVHandler [label="SharedPV::Handler", shape="box"]; + SharedChannel [shape="ellipse"]; + ChannelOp [label="SharedPut/RPC/MonitorFIFO", shape="ellipse"]; + + DynamicProvider [shape="box"]; + DynamicHandler [label="DynamicProvider::Handler", shape="box"]; + StaticProvider [shape="ellipse"]; + + ChannelRequester [shape="ellipse"]; + ChannelProvider [shape="box"]; + + ServerContext [shape="box"]; + + ChannelProvider -> SharedPV [color="red", style="dashed"]; + DynamicProvider -> DynamicHandler [color="red"]; + StaticProvider -> SharedPV [color="red"]; + ServerContext -> ChannelProvider [color="red"]; + ServerContext -> DynamicProvider [color="red"]; + ServerContext -> StaticProvider [color="red"]; + ServerContext -> ChannelRequester [color="red"]; + ServerContext -> SharedChannel [color="red"]; + ServerContext -> ChannelOp [color="red"]; + SharedPV -> SharedPVHandler [color="red"]; + SharedChannel -> SharedPV [color="red"]; + ChannelOp -> SharedChannel [color="red"]; + } + @enddot + * + * @{ + */ + +/** @brief A Provider based on a list of SharedPV instance. + * + * SharedPV instances may be added/removed at any time. So it is only "static" + * in the sense that the list of PV names is known to StaticProvider at all times. + * + * @see @ref pvas_sharedptr + */ +class epicsShareClass StaticProvider { +public: + POINTER_DEFINITIONS(StaticProvider); + struct Impl; +private: + std::tr1::shared_ptr impl; // const after ctor +public: + + //! Interface for something which can provide Channels. aka A "PV". Typically a SharedPV + struct epicsShareClass ChannelBuilder { + POINTER_DEFINITIONS(ChannelBuilder); + virtual ~ChannelBuilder(); + virtual std::tr1::shared_ptr connect(const std::tr1::shared_ptr& provider, + const std::string& name, + const std::tr1::shared_ptr& requester) =0; + virtual void close(bool destroy=false) =0; + }; +private: + typedef std::map > builders_t; +public: + + //! Build a new, empty, provider. + //! @param name Provider Name. Only relevant if registerAsServer() is called, then must be unique in this process. + explicit StaticProvider(const std::string& name); + ~StaticProvider(); + + //! Call Channelbuilder::close(destroy) for all currently added ChannelBuilders. + //! @see SharedPV::close() + //! @note Provider locking rules apply (@see provider_roles_requester_locking). + void close(bool destroy=false); + + //! Add a PV (eg. SharedPV) to this provider. + void add(const std::string& name, + const std::tr1::shared_ptr& builder); + //! Remove a PV. Closes any open Channels to it. + //! @returns the PV which has been removed. + //! @note Provider locking rules apply (@see provider_roles_requester_locking). + std::tr1::shared_ptr remove(const std::string& name); + + //! Fetch the underlying ChannelProvider. Usually to build a ServerContext around. + std::tr1::shared_ptr provider() const; + + // iterate through currently add()'d PVs. Iteraters are invalidated by concurrent add() or remove() + builders_t::const_iterator begin() const; + builders_t::const_iterator end() const; +}; + +/** @brief A Provider which has no pre-configured list of names. + * + * Through an associated Handler, this provider sees all searchs, and may claim + * them. + * + * @see @ref pvas_sharedptr + */ +class epicsShareClass DynamicProvider { +public: + POINTER_DEFINITIONS(DynamicProvider); + struct Impl; +private: + std::tr1::shared_ptr impl; // const after ctor +public: + //! A single client serach request. May be associated with more than one name + class Search { + friend struct Impl; + bool isclaimed; + std::string cname; + Search(const std::string& name) :isclaimed(false),cname(name) {} + public: + //! The name being queried + const std::string& name() const { return cname; } + //! Stake a claim. + bool claimed() const { return isclaimed; } + //! Has been claimed() + void claim() { isclaimed = true; } + }; + typedef std::vector search_type; + + /** Callbacks associated with DynamicProvider. + * + * For the purposes of locking, this class is a Requester (see @ref provider_roles_requester_locking). + * It's methods will not be called with locks held. It may call + * methods which lock. + */ + struct epicsShareClass Handler { + POINTER_DEFINITIONS(Handler); + typedef epics::pvData::shared_vector names_type; + virtual ~Handler() {} + //! Called with name(s) which some client is searching for + virtual void hasChannels(search_type& name) =0; + //! Called when a client is requesting a list of channel names we provide. Callee should set dynamic=false if this list is exhaustive. + virtual void listChannels(names_type& names, bool& dynamic) {} + //! Called when a client is attempting to open a new channel to this SharedPV + virtual std::tr1::shared_ptr createChannel(const std::tr1::shared_ptr& provider, + const std::string& name, + const std::tr1::shared_ptr& requester) =0; + //! Called when the last reference to a DynamicProvider is released. Should close any channels. + virtual void destroy() {} + }; + + //! Build a new provider. + //! @param name Provider Name. Only relevant if registerAsServer() is called, then must be unique in this process. + //! @param handler Our callbacks. Internally stored a shared_ptr (strong reference). + DynamicProvider(const std::string& name, + const std::tr1::shared_ptr& handler); + ~DynamicProvider(); + + Handler::shared_pointer getHandler() const; + + //void close(); + + //! Fetch the underlying ChannelProvider. Usually to build a ServerContext around. + std::tr1::shared_ptr provider() const; +}; + +//! @} + +} // namespace pvas + +#endif // PVA_SERVER_H diff --git a/src/server/server.cpp b/src/server/server.cpp new file mode 100644 index 0000000..dfbe228 --- /dev/null +++ b/src/server/server.cpp @@ -0,0 +1,270 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include +#include + +#define epicsExportSharedSymbols +#include "pva/server.h" +#include "pv/pvAccess.h" +#include "pv/reftrack.h" + +namespace pvd = epics::pvData; +namespace pva = epics::pvAccess; + +typedef epicsGuard Guard; +typedef epicsGuardRelease UnGuard; + +namespace pvas { + +struct StaticProvider::Impl : public pva::ChannelProvider +{ + POINTER_DEFINITIONS(Impl); + + static size_t num_instances; + + const std::string name; + pva::ChannelFind::shared_pointer finder; // const after ctor + std::tr1::weak_ptr internal_self, external_self; // const after ctor + + mutable epicsMutex mutex; + + typedef StaticProvider::builders_t builders_t; + builders_t builders; + + Impl(const std::string& name) + :name(name) + { + REFTRACE_INCREMENT(num_instances); + } + virtual ~Impl() { + REFTRACE_DECREMENT(num_instances); + } + + virtual void destroy() OVERRIDE FINAL {} + + virtual std::string getProviderName() OVERRIDE FINAL { return name; } + virtual pva::ChannelFind::shared_pointer channelFind(std::string const & name, + pva::ChannelFindRequester::shared_pointer const & requester) OVERRIDE FINAL + { + bool found; + + { + Guard G(mutex); + + found = builders.find(name)!=builders.end(); + } + requester->channelFindResult(pvd::Status(), finder, found); + return finder; + } + virtual pva::ChannelFind::shared_pointer channelList(pva::ChannelListRequester::shared_pointer const & requester) OVERRIDE FINAL + { + epics::pvData::PVStringArray::svector names; + { + Guard G(mutex); + names.reserve(builders.size()); + for(builders_t::const_iterator it(builders.begin()), end(builders.end()); it!=end; ++it) { + names.push_back(it->first); + } + } + requester->channelListResult(pvd::Status(), finder, pvd::freeze(names), false); + return finder; + } + virtual pva::Channel::shared_pointer createChannel(std::string const & name, + pva::ChannelRequester::shared_pointer const & requester, + short priority, std::string const & address) OVERRIDE FINAL + { + pva::Channel::shared_pointer ret; + pvd::Status sts; + + { + Guard G(mutex); + builders_t::const_iterator it(builders.find(name)); + if(it!=builders.end()) + ret = it->second->connect(Impl::shared_pointer(internal_self), name, requester); + } + + if(!ret) { + sts = pvd::Status::error("No such channel"); + } + + requester->channelCreated(sts, ret); + return ret; + } + +}; + +size_t StaticProvider::Impl::num_instances; + +StaticProvider::ChannelBuilder::~ChannelBuilder() {} + +StaticProvider::StaticProvider(const std::string &name) + :impl(new Impl(name)) +{ + impl->internal_self = impl; + impl->finder = pva::ChannelFind::buildDummy(impl); + // wrap ref to call destroy when all external refs (from DyamicProvider::impl) are released. + impl.reset(impl.get(), pvd::Destroyable::cleaner(impl)); + impl->external_self = impl; +} + +StaticProvider::~StaticProvider() { close(); } + +void StaticProvider::close(bool destroy) +{ + Impl::builders_t pvs; + { + Guard G(impl->mutex); + if(destroy) { + pvs.swap(impl->builders); // consume + } else { + pvs = impl->builders; // just copy, close() is a relatively rare action + } + } + for(Impl::builders_t::iterator it(pvs.begin()), end(pvs.end()); it!=end; ++it) { + it->second->close(destroy); + } +} + +std::tr1::shared_ptr StaticProvider::provider() const +{ + return Impl::shared_pointer(impl->internal_self); +} + +void StaticProvider::add(const std::string& name, + const std::tr1::shared_ptr& builder) +{ + Guard G(impl->mutex); + if(impl->builders.find(name)!=impl->builders.end()) + throw std::logic_error("Duplicate PV name"); + impl->builders[name] = builder; +} + +std::tr1::shared_ptr StaticProvider::remove(const std::string& name) +{ + std::tr1::shared_ptr ret; + { + Guard G(impl->mutex); + Impl::builders_t::iterator it(impl->builders.find(name)); + if(it!=impl->builders.end()) { + ret = it->second; + impl->builders.erase(it); + } + } + if(ret) + ret->close(true); + return ret; +} + +StaticProvider::builders_t::const_iterator StaticProvider::begin() const { + Guard G(impl->mutex); + return impl->builders.begin(); +} + +StaticProvider::builders_t::const_iterator StaticProvider::end() const { + Guard G(impl->mutex); + return impl->builders.end(); +} + + +struct DynamicProvider::Impl : public pva::ChannelProvider +{ + POINTER_DEFINITIONS(Impl); + + static size_t num_instances; + + const std::string name; + const std::tr1::shared_ptr handler; + pva::ChannelFind::shared_pointer finder; // const after ctor + std::tr1::weak_ptr internal_self, external_self; // const after ctor + + mutable epicsMutex mutex; + + Impl(const std::string& name, + const std::tr1::shared_ptr& handler) + :name(name) + ,handler(handler) + { + REFTRACE_INCREMENT(num_instances); + } + virtual ~Impl() { + REFTRACE_DECREMENT(num_instances); + } + + virtual void destroy() OVERRIDE FINAL { + handler->destroy(); + } + + virtual std::string getProviderName() OVERRIDE FINAL { return name; } + virtual pva::ChannelFind::shared_pointer channelFind(std::string const & name, + pva::ChannelFindRequester::shared_pointer const & requester) OVERRIDE FINAL + { + bool found = false; + { + search_type search; + search.push_back(DynamicProvider::Search(name)); + + handler->hasChannels(search); + + found = !search.empty() && search[0].name()==name && search[0].claimed(); + } + requester->channelFindResult(pvd::Status(), finder, found); + return finder; + } + virtual pva::ChannelFind::shared_pointer channelList(pva::ChannelListRequester::shared_pointer const & requester) OVERRIDE FINAL + { + epics::pvData::PVStringArray::svector names; + bool dynamic = true; + handler->listChannels(names, dynamic); + requester->channelListResult(pvd::Status(), finder, pvd::freeze(names), dynamic); + return finder; + } + virtual pva::Channel::shared_pointer createChannel(std::string const & name, + pva::ChannelRequester::shared_pointer const & requester, + short priority, std::string const & address) OVERRIDE FINAL + { + pva::Channel::shared_pointer ret; + pvd::Status sts; + + ret = handler->createChannel(ChannelProvider::shared_pointer(internal_self), name, requester); + + requester->channelCreated(sts, ret); + return ret; + } + +}; + +size_t DynamicProvider::Impl::num_instances; + +DynamicProvider::DynamicProvider(const std::string &name, + const std::tr1::shared_ptr &handler) + :impl(new Impl(name, handler)) +{ + impl->internal_self = impl; + impl->finder = pva::ChannelFind::buildDummy(impl); + // wrap ref to call destroy when all external refs (from DyamicProvider::impl) are released. + impl.reset(impl.get(), pvd::Destroyable::cleaner(impl)); + impl->external_self = impl; +} + +DynamicProvider::~DynamicProvider() {} + +DynamicProvider::Handler::shared_pointer DynamicProvider::getHandler() const +{ + return impl->handler; +} + +std::tr1::shared_ptr DynamicProvider::provider() const +{ + return Impl::shared_pointer(impl->internal_self); +} + +void registerRefTrackServer() +{ + epics::registerRefCounter("pvas::StaticProvider", &StaticProvider::Impl::num_instances); + epics::registerRefCounter("pvas::DynamicProvider", &DynamicProvider::Impl::num_instances); +} + +} // namespace pvas