|
BDE 4.39.x Production Release
|
Provide a pipe-based mechanism to process task control messages.
Provide a pipe-based mechanism to process task control messages.
This component provides a mechanism, balb::PipeTaskManager, that listens on a named pipe for messages that are typically used to influence the behavior of a (running) task.
For example, a balb::PipeTaskManager might be configured to listen on a well known named pipe (e.g., myapplication.ctrl), for the control messages starting with:
The use of imperative verbs for the first field of a message is a common practice. The first field is called the message "prefix". On receipt of a message with a known prefix, a previously registered handler functor is invoked with two arguments:
bsl::istream from which the rest of the message (if any) can be read. Thus, we have a mechanism by which a running task can be sent commands and, optionally, arguments for those commands.Once the relationship between prefix and handler has been specified, the start method is used to create the named pipe (or re-open an existing named pipe) and a thread created to listen for messages.
A human user on a console might then use a command line application to send control messages to the myapplication.ctrl pipe to configure the behavior of the running task. In the example above, the handler for the LOG prefix expects additional parameters. Thus:
changes the logging verbosity of the task to level "4". See bdls_pipeutil for functions that can be invoked from C++ code to send messages to a named pipe.
A default constructed balb::PipeTaskManager has no registered handlers. Users can use the exposed balb::ControlManager, to register different control message prefixes (typically "verbs") to dispatch received messages to an appropriate functor.
Alternatively, one can construct a balb::PipeTaskManager using a separately created and configured a balb::ControlManager object. Doing so allows that single balb::ControlManager to be shared among several balb::PipeTaskManager objects, each listening on a different named pipe.
This component is thread-safe but not thread-enabled, meaning that multiple threads may safely use their own instances of balb::PipeTaskManager, but may not manipulate the same instance of balb::PipeTaskManager simultaneously. Note that the contained balb::ControlManager object is available via both const and non=const references and that object is safe for multiple threads.
The balb::PipeTaskManger objects waits for messages from a named pipe provided to the start method. The argument to start – mapped to all lower case, if needed – determines the basename of the named pipe. The directory of that named pipe depends on the platform and environment variables.
\\.\pipe\.SOCKDIR environment variable, if set; otherwise,TMPDIR environment variable, if set; otherwise,See the makeCanonicalName overloads in bdls_pipeutil for details.
Moreover, the start method must be able to freshly create a named pipe. In general, start will fail if a named pipe of the calculated canonical name already exists. On Unix, if that named pipe is not in use (not open for reading), the start attempts to remove and re-create that named pipe.
On Unix systems, named pipes are created having the permission 0666 (read and write for user, group, and other) limited by the current umask value of the process.
On successful completion of start, the (full) pathname of the created named pipe is provided by the pipeName accessor. The full path name must be passed to sending processes so they can open that named pipe and write control messages.
Each message consists of a sequence of fields separated by blanks and/or tabs and terminated by a newline (\n) character. The terminating newline is not passed to the message handler.
The first field is called the message "prefix" and is used to find a previously registered handler for the message. The handler lookup is case insensitive. Empty messages (newline only) and messages for which no handler can be found are silently ignored.
Note that this facility provides a one-way flow of information from the writer to a named pipe to the registered message handler. There is no mechanism here for validating message (e.g., a given prefix has required additional fields) or returning status. Many applications provide output by writing to the console or to a log.
Users that expect multiple concurrent writers to a single pipe must be aware that the message content might be corrupted (interleaved) unless:
write system call.PIPE_BUF (the limit for guaranteed atomicity).The value PIPE_BUF depends on the platform:
Also note that Linux allows the PIPE_BUF size to be changed via the fcntl system call.
This section illustrates intended use of this component.
Suppose one is creating an application that allows for dynamically changing its logging verbosity level, resetting to its initial state, to shutdown cleanly, and the listing a description of supported messages.
The balb::PipeTaskManager class can be used to provide support for messages that are sent via a named pipe and have the syntax shown below:
Note that the above description corresponds to the output produced by our application in response to a "HELP" message.
First, define several global, atomic variables that will be used to exchange information between the thread that monitors the named pipe and the other threads of the application.
Then, we define helper functions myLoggingManagerGet and myLoggingManagerSet so that the handler for "LOG" messages can delegate processing the "GET" and "SET" subcommands. The other defined messages have minimal syntax so use of a delegation pattern is overkill in those cases.
Next, define handler functions for the "EXIT", "RESTART", and "LOG" messages.
Notice that no handler is yet defined for the "HELP" message. That functionally can be provided using methods of the contained balb::ControlManager object. See below.
Then, create a balb::PipeTaskManger object and register the above functions as handlers:
and add an additional handler that provides a list of the registered messages and the syntax for using them:
Next, if we are on a Unix system, we confirm that our named pipes will be created in the directory named by the TMPDIR environment variable:
Then, start listening for incoming messages at pipe having the name based on on the name "MyApplication.CTRL". To avoid collisions when this test case is run simultaneously on a single machine (or on different machines sharing an NFS mount), we will append the hostname and process ID to the base name.
Next, for expository purposes, confirm that a pipe of that name exists in the expected directory and is open for reading.
Notice that given baseName has been canonically converted to lowercase.
Now, our application can continue doing useful work while the background thread monitors the named pipe for incoming messages:
Finally, in some other programming context, say mySender, a context in another process that has been passed the value of pipeName, control messages can be sent to myApplication1 above.
Notice that:
onLog accordingly.The console log of our application shows the response for each received control message. In general, these messages will be interleaved with the output of the "useful work" done in the for loop of 'myApplicaton1.