|
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316 |
- // \file child.hpp
- //
- // Copyright (C) 2014 MicroNeil Research Corporation.
- //
- // This program is part of the MicroNeil Research Open Library Project. For
- // more information go to http://www.microneil.com/OpenLibrary/index.html
- //
- // This program is free software; you can redistribute it and/or modify it
- // under the terms of the GNU General Public License as published by the
- // Free Software Foundation; either version 2 of the License, or (at your
- // option) any later version.
- //
- // This program is distributed in the hope that it will be useful, but WITHOUT
- // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- // more details.
- //
- // You should have received a copy of the GNU General Public License along with
- // this program; if not, write to the Free Software Foundation, Inc., 59 Temple
- // Place, Suite 330, Boston, MA 02111-1307 USA
- //==============================================================================
-
- /*
- \brief The child module provides classes to spawn and communicate
- with child processes.
- */
-
- #ifndef CHILD_HPP
- #define CHILD_HPP
-
- #ifdef _WIN32
- #include <windows.h>
- #endif
-
- #include <cstdint>
- #include <streambuf>
- #include <iostream>
- #include <string>
- #include <vector>
- #include <thread>
- #include <mutex>
-
- namespace CodeDweller {
-
- /**
- \namespace CodeDweller
-
- The CodeDweller namespace contains components providing high-level
- functionality for applications.
-
- */
-
- /** Class that abstracts the iostreams communication with a child
- process.
-
- This class provides functionality to create a child process,
- communicate with the child process via streams and signals, and
- obtain the exit code of the child process.
-
- */
-
- class ChildStream : public std::iostream {
-
- private:
-
- /// Streambuf class for reading from the standard output and
- /// writing to the standard input of the child process.
- class ChildStreambuf : public std::streambuf {
-
- friend class ChildStream;
-
- public:
-
- /// Constructor.
- //
- // \param[in] bufSize is the size in bytes of the input
- // buffer and output buffer.
- //
- explicit ChildStreambuf(std::size_t bufSize = 4096);
-
- #ifdef _WIN32
-
- /// Set the handle to read the standard output of the child
- /// process.
- //
- // \param[in] inHandle is the input handle for the standard
- // output of the child process.
- //
- void setInputHandle(HANDLE inHandle);
-
- /// Set the handle to write the standard input of the child
- /// process.
- //
- // \param[in] outHandle is the output handle for the standard
- // input of the child process.
- //
- void setOutputHandle(HANDLE outHandle);
-
- #else
-
- /// Set the file descriptor to read the standard output of the
- /// child process.
- //
- // \param[in] inFd is the input file descriptor for the standard
- // output of the child process.
- //
- void setInputFileDescriptor(int inFd);
-
- /// Set the file descriptor to write the standard input of the
- /// child process.
- //
- // \param[in] outFd is the output file descriptor for the
- // standard input of the child process.
- //
- void setOutputFileDescriptor(int outFd);
-
- #endif
-
- private:
-
- /** Return the number of bytes that can be read without
- blocking.
-
- This method checks if any input is available from the pipe,
- and returns the number of bytes in the input buffer plus 1.
- Reading that number of bytes will not block. Reading a
- larger number of bytes might block.
-
- \returns minimum number of bytes that can be read without
- blocking.
-
- */
- size_t numBytesAvailable() const;
-
- /// Override streambuf::underflow().
- int_type underflow();
-
- /// Flush the output buffer.
- void flushBuffer();
-
- /// Override streambuf::overflow().
- int_type overflow(int_type ch);
-
- /// Override streambuf::sync().
- int sync();
-
- /// Input and output handles.
- #ifdef _WIN32
- HANDLE inputHandle;
- HANDLE outputHandle;
- #else
- int inputFileDescriptor;
- int outputFileDescriptor;
- #endif
-
- /// Size of buffers.
- std::size_t bufferSize;
-
- /// Read buffer.
- std::vector<char> readBuffer;
-
- /// Write buffer.
- std::vector<char> writeBuffer;
-
- /// Copy constructor not implemented.
- ChildStreambuf(const ChildStreambuf &) = delete;
-
- /// Assignment operator not implemented.
- ChildStreambuf &operator=(const ChildStreambuf &) = delete;
-
- };
-
- /// Stream buffer for reading from the stdout and writing to the
- /// stdin of the child process.
- ChildStreambuf childStreambuf;
-
- public:
-
- /** Constructor for spawning with command-line parameters.
-
- The constructor configures the object, and spawns the child
- process.
-
- \param[in] args contains the child executable file name and
- command-line parameters. args[0] contains the full path of the
- executable, and args[1] thru args[n] are the command-line
- parameters.
-
- \param[in] bufSize is the input and output buffer size of the
- stream used to communicate with the child process.
-
- \throws runtime_error if an error occurs.
-
- */
- ChildStream(std::vector<std::string> const &args, size_t bufSize = 4096);
-
- /** Constructor for spawning without command-line parameters.
-
- The constructor configures the object, and spawns the child
- process.
-
- \param[in] childpath contains the child executable file name.
-
- \param[in] bufSize is the input and output buffer size of the
- stream used to communicate with the child process.
-
- \throws runtime_error if an error occurs.
-
- */
- ChildStream(std::string const &childpath, size_t bufSize = 4096);
-
- /** Constructor.
-
- The constructor configures the I/O buffers, but doesn't spawn
- any child process.
-
- \param[in] bufSize is the input and output buffer size of the
- stream used to communicate with the child process.
-
- */
- ChildStream(size_t bufSize = 4096);
-
- /** Destructor terminates the child process. */
- ~ChildStream();
-
- /** Spawn the child process.
-
- \param[in] args contains the child executable file name and
- command-line parameters. args[0] contains the full path of the
- executable, and args[1] thru args[n] are the command-line
- parameters.
-
- \throws runtime_error if an error occurs.
-
- \throws runtime_error if an error occurs.
-
- */
- void open(std::vector<std::string> const &args);
-
- /** Spawn the child process.
-
- \param[in] childpath contains the child executable file name.
-
- \throws runtime_error if an error occurs.
-
- */
- void open(std::string const &childpath);
-
- /** Get the number of bytes available for input.
-
- @returns number of bytes that can be read without blocking.
-
- */
- size_t numBytesAvailable() const;
-
- /** Check whether the child process is running.
-
- \returns True if the child process is running, false
- otherwise.
-
- */
- bool isRunning() const;
-
- /** Terminate the child process.
-
- \throws runtime_error if an error occurs.
-
- \throws logic_error if the child process is not running.
-
- */
- void close();
-
- /** Check whether the child process has exited.
-
- \returns True if the child process has exited, false
- otherwise.
-
- \throws runtime_error if an error occurs.
-
- \throws logic_error if the child process is not running.
-
- */
- bool isDone();
-
- /** Get the exit value of the child process.
-
- \returns The exit value of the child process if the child
- process has exited.
-
- \throws runtime_error if an error occurs.
-
- \throws logic_error if the child process has not exited.
-
- \throws logic_error if the child process is not running.
-
- */
- int32_t result();
-
- private:
-
- /** Spawn the child process.
-
- \throws runtime_error if an error occurs.
-
- */
- void run();
-
- /// Exit code to use when terminating the child process.
- static const uint32_t terminateExitCode = 0;
-
- /// True if the child process was successfully started.
- bool childStarted;
-
- /// True if the child process has exited.
- bool childExited;
-
- /// Initialize data members.
- void init();
-
- /// Child executable path and command-line parameters.
- std::vector<std::string> cmdArgs;
-
- #ifdef _WIN32
- /// Child's process handle.
- HANDLE childProcess;
-
- /// Child's thread handle.
- HANDLE childThread;
- #else
- /// Child process ID.
- pid_t childPid;
- #endif
-
- /// Exit value of the process.
- int32_t exitCode;
-
- /// True if the exit code has been obtained.
- bool exitCodeObtainedFlag;
-
- /// Return text for the most recent error.
- //
- // \returns Human-readable description of the most recent error.
- //
- static std::string getErrorText();
-
- };
-
- /** Class that abstracts the non-blocking communication with a child
- process.
-
- This class provides functionality to create a child process,
- communicate with the child process via non-blocking methods, and
- obtain the exit code of the child process.
-
- The existing process can be terminated by the close() method,
- and a new process spawned by the open() method.
-
- The close() method calls TerminateProcess under Windows, and
- sends SIGKILL under Linux. After sending SIGKILL, the close()
- methods calls waitpid() to wait for the child process to exit.
-
- When a child process is spawned, this class creates two threads:
- One for writing to the child, and one for reading from the
- child. These threads communicate with the user thread via:
-
- <ol>
-
- <li> A shared boolean that indicates when the threads should
- stop.</li>
-
- <li> A shared linear buffer containing data to write to the
- child process.</li>
-
- <li> A shared circular buffer containing data read from the
- child process.</li>
-
- </ol>
-
- */
-
- class Child {
-
- private:
-
- /** Circular buffer of characters. */
- class CircularBuffer {
-
- public:
-
- /** Constructor specifies the capacity.
-
- @param[in] maxSize is the capacity of the buffer.
-
- */
- CircularBuffer(size_t maxSize);
-
- /** Check whether the container is empty.
-
- @returns true if the container is empty, false otherwise.
-
- This method can be invoked by the user thread without
- serializing access to the object with a mutex. The
- reasoning is:
-
- 1) The buffer is empty if and only if iBegin == iEnd.
-
- 2) Only user thread modifies iBegin, by extracting data
- from the buffer.
-
- This means that if iBegin == iEnd, then:
-
- 1) iBegin is a valid index, since only the user thread can
- modify iBegin. The user thread maintains the validity
- of iBegin.
-
- 2) iEnd is a valid index, since it equals iBegin.
-
- 3) The result iBegin == iEnd is also valid, and indicates
- whether the buffer is empty.
-
- */
- bool empty() const;
-
- /** Get the size.
-
- @returns the number of bytes in the buffer.
-
- */
- size_t nUsed() const;
-
- /** Get the available space.
-
- @returns a number of bytes that can be written to the buffer
- without overwriting any existing data.
-
- This method can be invoked by the reader thread without
- serializing access to the object with a mutex. The reason
- is that:
-
- 1) The free space depends on capacity, iBegin, and iEnd.
-
- 2) The capacity is not changed while the threads are
- running, and only the reader thread modifies iEnd.
- Therefore, the reader thread always sees a valid and
- up-to-date value for capacity and iEnd.
-
- 3) Because the user thread modifies iBegin, iBegin might
- be invalid. The only invalid value is capacity + 1, in
- which case the correct value of iBegin is 0. This method
- checks for the invalid value, and uses the correct
- value as needed.
-
- 4) Because the user thread modifies iBegin, iBegin might
- be out-of-date. Because the user thread only
- increments iBegin, an out-of-date value would result in
- a smaller value of the available space in the buffer.
-
- */
- size_t nFree() const;
-
- /** Clear the buffer. */
- void clear();
-
- /** Put bytes to the buffer.
-
- @param[in] ptr is the address of the first byte to put.
-
- @param[in] nBytes is the number of bytes to put.
-
- @warning The capacity of the buffer must not be exceeded;
- exceeding the capacity corrupts the buffer.
-
- */
- void put(char const *ptr, size_t nBytes);
-
- /** Get bytes from the buffer.
-
- This method gets the specified number of bytes from the
- buffer, and erases those bytes from the buffer.
-
- The type T must have the following methods:
-
- <ol>
-
- <li>clear().</li>
-
- <li>capacity().</li>
-
- <li>reserve().</li>
-
- <li>push_back(char).</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T.
-
- @param[out] buf receives the data. The contents of buf are
- replaced with the data in the circular buffer.
-
- @param[in] nBytes is the number of bytes to get. Specifying
- a value of zero for nBytes gets and erases all the data.
-
- */
- template<typename T>
- void getAndErase(T &buf, size_t nBytes) {
-
- if ( (0 == nBytes) || (nBytes > nUsed()) ) {
- nBytes = nUsed();
- }
-
- buf.clear();
- if (buf.capacity() < nBytes) {
- buf.reserve(nBytes);
- }
-
- for (size_t i = 0; i < nBytes; i++) {
- buf.push_back(buffer[iBegin]);
- nextIndex(iBegin);
- }
-
- }
-
- /** Check whether specified data is present in the buffer.
-
- This method check whether the specified data is present in
- the buffer, and provides the number of characters before the
- specified data.
-
- The type U must have the following methods:
-
- <ol>
-
- <li>Methods required by CircularBuffer::match().</li>
-
- <li>size().</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T.
-
- @param[in] data is the specified data.
-
- @param[out] nBytesBefore is the number of bytes before the
- specified data.
-
- @returns true if the data is present in the buffer, false
- otherwise.
-
- @see CircularBuffer::match().
-
- */
- template<typename U>
- bool dataIsPresent(U const &data, size_t &nBytesBefore) const {
-
- size_t iLocalBegin = iBegin;
-
- if (nUsed() < data.size()) {
- return false;
- }
-
- size_t nToCheck = nUsed() - data.size() + 1;
- for (nBytesBefore = 0; nBytesBefore < nToCheck; nBytesBefore++) {
-
- if (match(data, iLocalBegin)) {
- return true;
- }
-
- nextIndex(iLocalBegin);
- }
-
- return false;
-
- }
-
- private:
-
- /** Increment the index.
-
- @param[in] index is the index to increment.
-
- */
- void nextIndex(size_t &index) const {
- index++;
- if (index >= capacity + 1)
- index = 0;
- }
-
-
- /** Check whether the specified data is in the buffer.
-
- This method checks whether the specified data is present in
- the buffer starting at the specified location.
-
- The type U must have the following methods:
-
- <ol>
-
- <li>operator[].</li>
-
- <li>size().</li>
-
- </ol>
-
- @param[in] data is the specified data.
-
- @param[in] index is the index of the buffer to start
- checking for the specified data.
-
- @returns true if the specified data is in the buffer, false
- otherwise.
-
- */
- template<typename U>
- bool match(U &data, size_t indx) const {
-
- for (size_t i = 0; i < data.size(); i++) {
-
- if (iEnd == indx) {
- return false;
- }
- if (data[i] != buffer[indx]) {
- return false;
- }
- nextIndex(indx);
-
- }
-
- return true;
-
- }
-
- /// Buffer to hold data.
- std::vector<char> buffer;
-
- /// Capacity of the buffer.
- size_t capacity;
-
- /// Index of first element.
- size_t iBegin;
-
- /// Index of one past the last element.
- size_t iEnd;
-
- };
-
- public:
-
- /** Constructor for spawning with command-line parameters.
-
- The constructor configures the object, and spawns the child
- process.
-
- \param[in] args contains the child executable file name and
- command-line parameters. args[0] contains the full path of the
- executable, and args[1] thru args[n] are the command-line
- parameters.
-
- \param[in] bufSize is the input and output buffer size of the
- stream used to communicate with the child process.
-
- \param[in] nominalAboveMinPollTime_ms is used to determine the
- minimum time in milliseconds that the writer thread sleeps
- when there's no data in the output buffer, and that the reader
- thread sleeps when there's no room in the input buffer. The
- minimum time is nominalAboveMinPollTime_ms +
- CodeDweller::MinimumSleeperTime.
-
- \param[in] deltaPollTime_ms is how much longer, in
- milliseconds, the maximum time to sleep is than the minimum time.
-
- \throws runtime_error if an error occurs.
-
- */
- Child(std::vector<std::string> const &args,
- size_t bufSize = 128 * 1024,
- std::uint16_t nominalAboveMinPollTime_ms = 0,
- std::uint16_t deltaPollTime_ms = 500);
-
- /** Constructor for spawning without command-line parameters.
-
- The constructor configures the object, and spawns the child
- process.
-
- \param[in] childpath contains the child executable file name.
-
- \param[in] bufSize is the input and output buffer size of the
- stream used to communicate with the child process.
-
- \param[in] nominalAboveMinPollTime_ms is used to determine the
- minimum time in milliseconds that the writer thread sleeps
- when there's no data in the output buffer, and that the reader
- thread sleeps when there's no room in the input buffer. The
- minimum time is nominalAboveMinPollTime_ms +
- CodeDweller::MinimumSleeperTime.
-
- \param[in] deltaPollTime_ms is how much longer, in
- milliseconds, the maximum time to sleep is than the minimum time.
-
- \throws runtime_error if an error occurs.
-
- */
- Child(std::string const &childpath,
- size_t bufSize = 128 * 1024,
- std::uint16_t nominalAboveMinPollTime_ms = 0,
- std::uint16_t deltaPollTime_ms = 500);
-
- /** Constructor.
-
- The constructor configures the I/O buffers, but doesn't spawn
- any child process.
-
- \param[in] bufSize is the input and output buffer size of the
- stream used to communicate with the child process.
-
- \param[in] nominalAboveMinPollTime_ms is used to determine the
- minimum time in milliseconds that the writer thread sleeps
- when there's no data in the output buffer, and that the reader
- thread sleeps when there's no room in the input buffer. The
- minimum time is nominalAboveMinPollTime_ms +
- CodeDweller::MinimumSleeperTime.
-
- \param[in] deltaPollTime_ms is how much longer, in
- milliseconds, the maximum time to sleep is than the minimum time.
-
- */
- Child(size_t bufSize = 4096,
- std::uint16_t nominalAboveMinPollTime_ms = 0,
- std::uint16_t deltaPollTime_ms = 500);
-
- /** Destructor terminates the child process. */
- ~Child();
-
- /** Spawn the child process.
-
- \param[in] args contains the child executable file name and
- command-line parameters. args[0] contains the full path of the
- executable, and args[1] thru args[n] are the command-line
- parameters.
-
- \throws runtime_error if an error occurs.
-
- \throws runtime_error if an error occurs.
-
- */
- void open(std::vector<std::string> const &args);
-
- /** Spawn the child process.
-
- \param[in] childpath contains the child executable file name.
-
- \throws runtime_error if an error occurs.
-
- */
- void open(std::string const &childpath);
-
- /** All-or-nothing non-blocking queue write request to the child.
-
- This methods attempts to queue a write request consisting of
- the entire contents of a container.
-
- The type T must have the following methods:
-
- <ol>
-
- <li>begin().</li>
-
- <li>end().</li>
-
- <li>size().</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T.
-
- @param[in] data is the data to queue.
-
- @returns true if the write request was queued, false
- otherwise.
-
- */
- template<typename T>
- bool write(T const &data) {
-
- if (!isRunning()) {
- throw std::logic_error("No child process is running.");
- }
-
- // The free space in the write buffer can be checked without
- // locking the mutex because:
- //
- // 1) bufferCapacity is unchanging, and
- //
- // 2) nWriteBytes is only decreased by the writer thread.
- //
- // This means that the calculated free space can only increase
- // by the action of the writer thread; the calculated free space
- // is a minimum value. In the worst case, this method would
- // return false unnecessarily.
- if (data.size() > bufferCapacity - nWriteBytes) {
- return false;
- }
-
- std::lock_guard<std::mutex> lock(writeBufferMutex);
-
- std::copy(data.begin(),
- data.end(),
- &(writeBuffer[nWriteBytes]));
-
- nWriteBytes += data.size();
-
- return true;
-
- }
-
- /** All-or-nothing non-blocking queue write request to the child.
-
- This methods attempts to queue a write request consisting of
- the entire contents of string literal.
-
- @param[in] data points to the string literal.
-
- @returns true if the write request was queued, false
- otherwise.
-
- */
- bool write(char const *data) {
- return write(std::string(data));
- }
-
- /** Non-blocking queue write request to the child.
-
- This methods attempts to queue a write request consisting of
- the the specified contents of a container. The number of
- bytes queued is the smaller of the size of the container and
- the number of free bytes in the output buffer.
-
- The type T must have the following methods:
-
- <ol>
-
- <li>begin().</li>
-
- <li>end().</li>
-
- <li>size().</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T.
-
- @param[in] data is the data to queue.
-
- @param[in] nBytes is the requested number of bytes to queue.
-
- @returns the number of bytes queued.
-
- */
- template<typename T>
- size_t write(T const &data, size_t nBytes) {
-
- if (!isRunning()) {
- throw std::logic_error("No child process is running.");
- }
-
- // See the comment above regarding checking the free space in the
- // write buffer without locking the mutex.
- size_t nFree = bufferCapacity - nWriteBytes;
-
- if (nBytes > nFree) {
- nBytes = nFree;
- }
- if (nBytes > data.size()) {
- nBytes = data.size();
- }
-
- std::lock_guard<std::mutex> lock(writeBufferMutex);
-
- std::copy(data.begin(),
- data.begin() + nBytes,
- &(writeBuffer[nWriteBytes]));
-
- nWriteBytes += nBytes;
-
- return nBytes;
-
- }
-
- /** Non-blocking queue write request to the child.
-
- This methods attempts to queue a write request consisting of
- as much as possible of the contents of the string.
-
- The type T must have the following methods:
-
- <ol>
-
- <li>begin().</li>
-
- <li>size().</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T.
-
- @param[in, out] data on input is the data to queue. On
- output, data contains the data that was not queued.
-
- @returns the number of bytes queued.
-
- */
- template<typename T>
- size_t writeAndShrink(T &data) {
-
- if (!isRunning()) {
- throw std::logic_error("No child process is running.");
- }
-
- // See the comment above regarding checking the free space in the
- // write buffer without locking the mutex.
- size_t nFree = bufferCapacity - nWriteBytes;
-
- if (0 == nFree) {
- return 0;
- }
-
- std::lock_guard<std::mutex> lock(writeBufferMutex);
-
- size_t nBytesToCopy = data.size();
-
- if (nBytesToCopy > nFree) {
- nBytesToCopy = nFree;
- }
-
- std::copy(data.begin(),
- data.begin() + nBytesToCopy,
- &(writeBuffer[nWriteBytes]));
- nWriteBytes += nBytesToCopy;
-
- data.erase(data.begin(), data.begin() + nBytesToCopy);
-
- return nBytesToCopy;
-
- }
-
- /** Check if all queued data was transmitted.
-
- @returns true if all the queued data was transmitted to the
- child, false otherwise.
-
- */
- bool isFinishedWriting() const;
-
- /** Non-blocking request to get data read from the child.
-
- This method attempts to get up to a specified number of bytes
- of data from the input buffer containing data received from
- the child. The data that is provided is erased from the input
- buffer.
-
- The type T must have the following methods:
-
- <ol>
-
- <li>Methods required by CircularBuffer::getAndErase().</li>
-
- <li>size().</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T.
-
- @param[out] data contains the copied data.
-
- @param[in] nBytes is the number of bytes to attempt to copy.
- If nBytes is zero, the contents of the entire input buffer is
- moved to data.
-
- @returns the number of bytes copied.
-
- @see CircularBuffer::getAndErase().
-
- */
- template<typename T>
- size_t read(T &data, size_t nBytes = 0) {
-
- if (!isRunning()) {
- throw std::logic_error("No child process is running.");
- }
-
- data.clear();
-
- // Can be called in the user thread without locking the mutex.
- if (readBuffer.empty()) {
- return 0;
- }
-
- std::lock_guard<std::mutex> lock(readBufferMutex);
-
- size_t nBytesToRead = nBytes;
-
- if (nBytesToRead > readBuffer.nUsed()) {
- nBytesToRead = readBuffer.nUsed();
- }
-
- readBuffer.getAndErase(data, nBytesToRead);
-
- return data.size();
-
- }
-
- /** Non-blocking request to get data up to a delimiter read from
- the child.
-
- This method check whether the specified delimiter is in the
- data received from the child.
-
- This method attempts to get data up to and not including a
- specified delimiter from the input buffer containing data
- received from the child. The data that is provided and the
- delimiter are erased from the input buffer.
-
- The type T must have the following methods:
-
- <ol>
-
- <li>Methods required by CircularBuffer::getAndErase().</li>
-
- <li>size().</li>
-
- <li>clear().</li>
-
- </ol>
-
- The type U must have the following methods:
-
- <ol>
-
- <li>Methods required by CircularBuffer::dataIsPresent().</li>
-
- <li>size().</li>
-
- <li>empty().</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T and U.
-
- @param[out] data contains the copied data.
-
- @param[in] delimiter contains the delimiter.
-
- @returns the number of bytes copied.
-
- @see CircularBuffer::getAndErase().
-
- @see CircularBuffer::dataIsPresent().
-
- */
- template<typename T, typename U>
- bool readDelimited(T &data, U const &delimiter) {
-
- if (!isRunning()) {
- throw std::logic_error("No child process is running.");
- }
-
- data.clear();
-
- // Can be called in the user thread without locking the mutex.
- if (readBuffer.empty()) {
- return false;
- }
-
- // Empty delimiter always matches.
- if (delimiter.empty()) {
- return true;
- }
-
- std::lock_guard<std::mutex> lock(readBufferMutex);
-
- size_t nBytesToRead;
-
- // Check for the presence of the delimiter.
- if (!readBuffer.dataIsPresent(delimiter, nBytesToRead)) {
- return false;
- }
-
- // Read the data.
- if (nBytesToRead > 0) {
- readBuffer.getAndErase(data, nBytesToRead);
- }
-
- // Discard the delimiter.
- T temp;
-
- readBuffer.getAndErase(temp, delimiter.size());
-
- return true;
-
- }
-
-
- /** Non-blocking request to get data up to a delimiter read from
- the child.
-
- This method check whether the specified delimiter is in the
- data received from the child.
-
- This method attempts to get data up to and not including a
- specified delimiter from the input buffer containing data
- received from the child. The data that is provided and the
- delimiter are erased from the input buffer.
-
- The type T must have the following methods:
-
- <ol>
-
- <li>Methods required by CircularBuffer::getAndErase().</li>
-
- <li>size().</li>
-
- <li>clear().</li>
-
- </ol>
-
- Both std::vector<char> and std::string can be used for T.
-
- @param[out] data contains the copied data.
-
- @param[in] delimiter contains the delimiter.
-
- @returns the number of bytes copied.
-
- @see CircularBuffer::getAndErase().
-
- */
- template<typename T>
- bool readDelimited(T &data, char const *delimiter) {
- return readDelimited(data, std::string(delimiter));
- }
-
- /** Check whether the child process is running.
-
- \returns True if the child process is running, false
- otherwise.
-
- */
- bool isRunning() const;
-
- /** Check error status.
-
- This method checks whether an error occurred when
- communicating with the child process.
-
- \param[out] errorDescription contains the description of any
- error that occurred.
-
- \returns true if an error occurred, false otherwise.
-
- */
- bool errorOccurred(std::string &errorDescription) const;
-
- /** Close the connection.
-
- This method terminate the child process if it is running, and
- resets the object. After this method is invoked, open() can
- be invoked to spawn and communicate with another child
- process.
-
- \throws runtime_error if an error occurs.
-
- \throws logic_error if the child process is not running.
-
- */
- void close();
-
- /** Check whether the child process has exited.
-
- \returns True if the child process has exited, false
- otherwise.
-
- \throws runtime_error if an error occurs.
-
- \throws logic_error if the child process is not running.
-
- */
- bool isDone();
-
- /** Get the exit value of the child process.
-
- \returns The exit value of the child process if the child
- process has exited.
-
- \throws runtime_error if an error occurs.
-
- \throws logic_error if the child process has not exited.
-
- \throws logic_error if the child process is not running.
-
- */
- int32_t result();
-
- private:
-
- /// Initialize data members.
- void init();
-
- /** Spawn the child process.
-
- \throws runtime_error if an error occurs.
-
- */
- void run();
-
- /// Reader thread object.
- std::thread readerThread;
-
- /// Thread start function to read data from the child.
- void readFromChild();
-
- /// Writer thread object.
- std::thread writerThread;
-
- /// Thread start function to send data to the child.
- void writeToChild();
-
- /// True if readerThread and writerThread are to stop.
- bool stopFlag;
-
- /// True if both the reader and writer the writer threads are
- /// running, false otherwise.
- bool threadsAreRunning;
-
- /// Description of any error.
- std::string errorText;
-
- /// Input and output handles.
- #ifdef _WIN32
- HANDLE inputHandle;
- HANDLE outputHandle;
- #else
- int inputFileDescriptor;
- int outputFileDescriptor;
- #endif
-
- /// Capacity of buffers.
- std::size_t bufferCapacity;
-
- /// Read buffer.
- CircularBuffer readBuffer;
-
- /// Mutex to serialize access to readBuffer.
- std::mutex readBufferMutex;
-
- /// Write buffer.
- std::vector<char> writeBuffer;
-
- /// Number of bytes in writeBuffer.
- size_t nWriteBytes;
-
- /// Mutex to serialize access to writeBuffer.
- std::mutex writeBufferMutex;
-
- /// Number of bytes in writer thread transmit buffer.
- size_t nTransmitBytes;
-
- /// Nominal poll time.
- //
- // If there isn't room in readBuffer, readerThread aperiodically
- // checks whether room is available. The check times are
- // determined by a CodeDweller::PollTimer object, which requires a
- // nominal poll time and a maximum poll time.
- int nominalPollTime_ms;
-
- /// Maximum poll time.
- int maximumPollTime_ms;
-
- /// Exit code to use when terminating the child process.
- static const uint32_t terminateExitCode = 0;
-
- /// True if the child process was successfully started.
- bool childStarted;
-
- /// True if the child process has exited.
- bool childExited;
-
- /// Child executable path and command-line parameters.
- std::vector<std::string> cmdArgs;
-
- #ifdef _WIN32
- /// Child's process handle.
- HANDLE childProcess;
-
- /// Child's thread handle.
- HANDLE childThread;
- #else
- /// Child process ID.
- pid_t childPid;
- #endif
-
- /// Exit value of the process.
- int32_t exitCode;
-
- /// True if the exit code has been obtained.
- bool exitCodeObtainedFlag;
-
- /// Return text for the most recent error.
- //
- // \returns Human-readable description of the most recent error.
- //
- static std::string getErrorText();
-
- };
-
- }
-
- #endif // CHILD_HPP
|