// balb_pipecontrolchannel.h -*-C++-*- // ---------------------------------------------------------------------------- // NOTICE // // This component is not up to date with current BDE coding standards, and // should not be used as an example for new development. // ---------------------------------------------------------------------------- #ifndef INCLUDED_BALB_PIPECONTROLCHANNEL #define INCLUDED_BALB_PIPECONTROLCHANNEL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a mechanism for reading control messages from a named pipe. // //@CLASSES: // balb::PipeControlChannel: Mechanism for reading messages from a named pipe // //@SEE_ALSO: bdls_pipeutil // //@DESCRIPTION: This component provides a platform-independent mechanism, // 'balb::PipeControlChannel', for establishing, monitoring, and shutting down // a named pipe. It reads text messages (generally short operational commands) // from the pipe and passes them to a callback function. Note that this // component does *not* provide a general transport mechanism over a named // pipe: it is specialized for the case that the messages to be read are // relatively short newline-terminated strings. // ///Thread Safety ///------------- // This component is thread-safe but not thread-enabled, meaning that multiple // threads may safely use their own instances of PipeControlChannel, but may // not manipulate the same PipeControlChannel simultaneously (except that // 'shutdown' may always be called safely). The 'start' function creates a // new thread which listens to the pipe for messages until 'shutdown' is // called. // ///Pipe Names ///---------- // This component requires a fully-qualified native pipe name. // 'bdlsu::PipeUtil' provides a portable utility method to generate such names. // ///Message Format ///-------------- // This component requires a trailing newline ('\n') character at the end of // each message. This trailing newline is stripped from the message before // the message is passed to the control callback. // ///Platform-Specific Pipe Name Encoding Caveats ///-------------------------------------------- // Pipe-name encodings have the following caveats for the following operating // systems: // //: o On Windows, methods of 'balb::PipeControlChannel' that take or return a //: pipe name as 'bsl::string' (or a reference to it) type assume that the //: name is encoded in UTF-8. The routines attempt to convert the name to a //: UTF-16 'wchar_t' string via 'bdlde::CharConvertUtf16::utf8ToUtf16', and //: if the conversion succeeds, call the Windows wide-character 'W' APIs with //: the UTF-16 name. If the conversion fails, the method fails. //: //: o Narrow-character pipe names in other encodings, containing characters //: with values in the range 128 - 255, will likely result in pipes being //: created with names that appear garbled if the conversion from UTF-8 to //: UTF-16 happens to succeed. //: //: o Neither 'utf8ToUtf16' nor the Windows 'W' APIs do any normalization of //: the UTF-16 strings resulting from UTF-8 conversion, and it is therefore //: possible to have sets of pipe names that have the same visual //: representation but are treated as different names by the system. //: //: o On Posix, a pipe name supplied to methods of 'balb::PipeControlChannel' //: as 'bsl::string' type is passed unchanged to the underlying system file //: APIs. Because the pipe names are passed unchanged, //: 'balb::PipeControlChannel' methods will work correctly on Posix with any //: encoding, but will *interoperate* only with processes that use the same //: encoding as the current process. //: //: o For compatibility with most modern Posix installs, and consistency with //: this component's Windows API, best practice is to encode all pipe names //: in UTF-8. // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: Controlling a Simple Server /// - - - - - - - - - - - - - - - - - - - // This example illustrates how to construct a simple server that records // messages sent on a pipe control channel until "EXIT" is received, at which // point the channel is closed and the server stops. // // First, let's define the implementation of our server. //.. // // =================== // // class ControlServer // // =================== // // class ControlServer { // // // DATA // balb::PipeControlChannel d_channel; // bsl::vector<bsl::string> d_messages; // // // PRIVATE MANIPULATORS // void onMessage(const bslstl::StringRef& message) // { // if ("EXIT" != message) { // d_messages.push_back(message); // } // else { // shutdown(); // } // } // // private: // // NOT IMPLEMENTED // ControlServer(const ControlServer&); // = delete // ControlServer& operator=(const ControlServer&); // = delete // // public: // // CREATORS // explicit ControlServer(bslma::Allocator *basicAllocator = 0) // : d_channel(bdlf::BindUtil::bind(&ControlServer::onMessage, // this, // bdlf::PlaceHolders::_1), // basicAllocator) // , d_messages(basicAllocator) // {} // // // MANIPULATORS // int start(const bslstl::StringRef& pipeName) // { // return d_channel.start(pipeName); // } // // void shutdown() // { // d_channel.shutdown(); // } // // void stop() // { // d_channel.stop(); // } // // // ACCESSORS // bsl::size_t numMessages() const // { // return d_messages.size(); // } // // const bsl::string& message(int index) const // { // return d_messages[index]; // } // }; //.. // Now, construct and run the server using a canonical name for the pipe: //.. // bsl::string pipeName; // int rc = bdls::PipeUtil::makeCanonicalName(&pipeName, // "ctrl.pcctest"); // assert(0 == rc); // // ControlServer server; // // rc = server.start(pipeName); // if (0 != rc) { // cout << "ERROR: Failed to start pipe control channel" << endl; // } //.. // Once the server is started, clients can send messages to the server. //.. // const char MSG0[] = "this is the first message"; // const char MSG1[] = "this is the second message"; // // rc = bdls::PipeUtil::send(pipeName, bsl::string(MSG0) + "\n"); // assert(0 == rc); // rc = bdls::PipeUtil::send(pipeName, bsl::string(MSG1) + "\n"); // assert(0 == rc); // rc = bdls::PipeUtil::send(pipeName, "EXIT\n"); // assert(0 == rc); //.. // The server shuts down once it processes the "EXIT" control message. //.. // server.stop(); // block until shutdown //.. // Finally, let's ensure the server received each control message sent. //.. // assert(2 == server.numMessages()); // assert(bsl::string(MSG0) == server.message(0)); // assert(bsl::string(MSG1) == server.message(1)); //.. #include <balscm_version.h> #include <bslmt_threadattributes.h> #include <bslmt_threadutil.h> #include <bslmf_assert.h> #include <bsls_atomic.h> #include <bsls_libraryfeatures.h> #include <bsl_functional.h> #include <bsl_string.h> #include <bsl_vector.h> #include <bslma_allocator.h> #ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #include <bslalg_typetraits.h> #endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR #include <memory_resource> // 'std::pmr::polymorphic_allocator' #endif // BSLS_LIBRARYFEATURES_HAS_CPP17_PMR #include <string> // 'std::string', 'std::pmr::string' namespace BloombergLP { namespace balb { // ======================== // class PipeControlChannel // ======================== class PipeControlChannel { // This class is a mechanism for reading control messages from a named // pipe. Use 'start' to spawn a thread that handles messages arriving at // the pipe, and 'shutdown' to stop reading. 'stop' blocks until the // processing thread has been terminated by a call to 'shutdown' (either // before 'stop' is called, or from some other thread). public: // TYPES typedef bsl::function<void(const bslstl::StringRef& message)> ControlCallback; // This type of function is called to handle control messages received // on the pipe. The 'message' is one complete message read from the // pipe, without the terminating newline character. private: // TYPES enum BackgroundThreadState { e_STOPPED, // The background thread is not running or about to exit. e_RUNNING, // The background thread is running normally. e_STOPPING // The background thread is requested to stop. }; // INSTANCE DATA ControlCallback d_callback; // callback for control messages bsl::string d_pipeName; // full path name of pipe bsl::vector<char> d_buffer; // message buffer bslmt::ThreadUtil::Handle d_thread; // background processing thread bsls::AtomicInt d_backgroundState; // the background thread state bool d_isPipeOpen; // true if the pipe is still open union { struct { int d_readFd; // fifo descriptor (read-only) int d_writeFd; // fifo descriptor (write-only) } d_unix; struct { void* d_handle; // pipe handle } d_windows; } d_impl; // platform-specific imp // PRIVATE MANIPULATORS void backgroundProcessor(); // Loop while d_isRunningFlag is true, reading and dispatching messages // from the named pipe. int createNamedPipe(const char *pipeName); // Open a named pipe having the specified 'pipeName'. Return 0 on // success, and a non-zero value otherwise. void dispatchMessageUpTo(const bsl::vector<char>::iterator& iter); // Dispatch the message that extends up to (but not including) the // specified 'iter' (which is an iterator into 'd_buffer'), then erase // the prefix that extends up to (and including) 'iter'. bool dispatchLeftoverMessage(); // If there is a newline character in 'd_buffer', call // 'dispatchMessageUpTo' with the location of that newline character // and return 'true'; otherwise, return 'false'. void destroyNamedPipe(); // Close the named pipe. int readNamedPipe(); // Block until bytes are available on the named pipe, and read them // into the internal buffer. If a message is encountered, dispatch it. // Return 0 on success, and a non-zero value otherwise. int sendEmptyMessage(); // Writes a '\n' character, only, to the pipe. Returns 0 on success, a // value greater than 0 on error, and a value less than 0 in case of a // timeout. Used to unblock the reading thread so it can detect a // shutdown condition. Note that this method is not to be called // anywhere except from 'shutdown'. private: // NOT IMPLEMENTED PipeControlChannel(const PipeControlChannel&); PipeControlChannel& operator=(const PipeControlChannel&); public: // CREATORS explicit PipeControlChannel(const ControlCallback& callback, bslma::Allocator *basicAllocator = 0); // Create a pipe control mechanism that dispatches messages to the // specified 'callback'. Optionally specify 'basicAllocator' to supply // memory. If 'basicAllocator' is zero, the currently installed // default allocator is used. ~PipeControlChannel(); // Destroy this object. Shut down the processing thread if it is still // running and block until it terminates. Close the named pipe and // clean up any associated system resources. // MANIPULATORS int start(const char *pipeName); template <class STRING_TYPE> int start(const STRING_TYPE& pipeName); int start(const char *pipeName, const bslmt::ThreadAttributes& attributes); template <class STRING_TYPE> int start(const STRING_TYPE& pipeName, const bslmt::ThreadAttributes& attributes); // Open a named pipe having the specified 'pipeName', and start a // thread to read messages and dispatch them to the callback specified // at construction. Optionally specify 'attributes' of the background // processing thread. If 'attributes' is not supplied, a default // constructed 'ThreadAttributes' object will be used. Return 0 on // success, and a non-zero value otherwise. In particular, return a // non-zero value if the pipe cannot be opened or if it is detected // that another process is reading from the pipe. 'pipeName' must be // of the types 'const char *', 'char *', 'bsl::string', 'std::string', // 'std::pmr::string' (if supported), or 'bslstl::StringRef'. void shutdown(); // Stop reading from the pipe and dispatching messages. If the // background thread has begun processing a message, this method will // block until a message that is currently being processed completes. void stop(); // Block until the background thread has been terminated by a call to // 'shutdown'. Then close the pipe and clean up the associated file. // ACCESSORS const bsl::string& pipeName() const; // Return the fully qualified system name of the pipe. }; // ==================================== // class PipeControlChannel_CStringUtil // ==================================== struct PipeControlChannel_CStringUtil { // This component-private utility 'struct' provides a namespace for the // 'flatten' overload set intended to be used in concert with an overload // set consisting of a function template with a deduced argument and an // non-template overload accepting a 'const char *'. The actual // implementation of the functionality would be in the 'const char *' // overload whereas the purpose of the function template is to invoke the // 'const char *' overload with a null-terminated string. // // The function template achieves null-termination by recursively calling // the function and supplying it with the result of 'flatten' invoked on // the deduced argument. This 'flatten' invocation will call 'c_str()' on // various supported 'string' types, will produce a temporary 'bsl::string' // for possibly non-null-terminated 'bslstl::StringRef', and will result in // a 'BSLMF_ASSERT' for any unsupported type. Calling the function with // the temporary 'bsl::string' produced from 'bslstl::StringRef' will // result in a second invocation of 'flatten', this time producing // 'const char *', and finally calling the function with a null-terminated // string. // // Note that the 'bslstl::StringRef' overload for 'flatten' is provided for // backwards compatibility. Without it, the 'bsl::string' and // 'std::string' overloads would be ambiguous. In new code, it is // preferable to not provide 'bslstl::StringRef' overload in a similar // facility and require the clients to explicitly state the string type in // their code, making a potential allocation obvious. The // 'bsl::string_view' overload is not provided for the same reason. // // Also note that since the constructor for 'string' types from // 'bsl::string_view' is explicit, it is not necessary to support // 'bsl::string_view' for backwards compatibility, and it is not supported. // CLASS METHODS static const char *flatten(char *cString); static const char *flatten(const char *cString); // Return the specified 'cString'. static const char *flatten(const bsl::string& string); static const char *flatten(const std::string& string); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR static const char *flatten(const std::pmr::string& string); #endif // Return the result of invoking 'c_str()' on the specified 'string'. static bsl::string flatten(const bslstl::StringRef& stringRef); // Return a temporary 'bsl::string' constructed from the specified // 'stringRef'. template <class TYPE> static const char *flatten(const TYPE&); // Produce a compile-time error informing the caller that the // parameterized 'TYPE' is not supported as the parameter for the call. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ inline const bsl::string& PipeControlChannel::pipeName() const { return d_pipeName; } template <class STRING_TYPE> int PipeControlChannel::start(const STRING_TYPE& pipeName) { return start(PipeControlChannel_CStringUtil::flatten(pipeName), bslmt::ThreadAttributes()); } template <class STRING_TYPE> int PipeControlChannel::start(const STRING_TYPE& pipeName, const bslmt::ThreadAttributes& threadAttributes) { return start(PipeControlChannel_CStringUtil::flatten(pipeName), threadAttributes); } // ------------------------------------ // class PipeControlChannel_CStringUtil // ------------------------------------ // CLASS METHODS inline const char *PipeControlChannel_CStringUtil::flatten(char *cString) { return cString; } inline const char *PipeControlChannel_CStringUtil::flatten(const char *cString) { return cString; } inline const char *PipeControlChannel_CStringUtil::flatten(const bsl::string& string) { return string.c_str(); } inline const char *PipeControlChannel_CStringUtil::flatten(const std::string& string) { return string.c_str(); } #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR inline const char *PipeControlChannel_CStringUtil::flatten( const std::pmr::string& string) { return string.c_str(); } #endif inline bsl::string PipeControlChannel_CStringUtil::flatten( const bslstl::StringRef& stringRef) { return stringRef; } template <class TYPE> inline const char *PipeControlChannel_CStringUtil::flatten(const TYPE&) { BSLMF_ASSERT(("Unsupported parameter type." && !sizeof(TYPE))); return 0; } } // close package namespace } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2017 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------