.. include:: vars.rst .. _section-gsg_intro_qos: Basic QoS ********* .. list-table:: :name: TableQoSPrerequisites :widths: 20 80 :header-rows: 0 * - Prerequisites - .. only:: cpp98 or cpp11 * :ref:`section-gsg_intro_keys`, including: * Instances * Key fields * CMake 3.11 or higher installed .. only:: csharp * :ref:`section-gsg_intro_keys`, including: * Instances * Key fields * Repository cloned from GitHub `here `_ * - Time to complete - 1 hour * - Concepts covered in this module - - Reliability - Durability - History depth - Deadline - QoS compatibility and matching - QoS profiles |CONNEXT| provides a variety of configuration options to change how your data is delivered and to fine-tune the performance of your system. These configuration options are called Quality of Service, or QoS. QoS settings are configured on |DWs| and |DRs| (and on other DDS objects, such as *Publishers* and *Subscribers*). Some of the basic QoS policies configured on |DWs| and |DRs| include the following: - **Reliability QoS Policy:** Should the arrival of each sample be guaranteed, or is best-effort enough and the risk of missing a sample acceptable? - **History QoS Policy:** How many samples should be stored for reliability purposes? - **Resource Limits QoS Policy:** What is the maximum allowed size of a |DW|'s or |DR|'s queue due to memory constraints? - **Durability QoS Policy:** Should samples be stored and automatically sent to new |DRs| as they start up? If yes, how many samples? - **Deadline QoS Policy:** How do we detect that streaming data is being sent at an acceptable rate? .. There are many more QoS policies that control discovery, fault-tolerance, and more. We will focus on just a few in this module. For a broader look at the QoS policies available, see the :link_connext_qosref_man:`QoS Reference Guide <>`. Although we will be focusing on QoS policies that are set on |DWs| and |DRs|, you can set QoS policies on other DDS objects; in :ref:`section-gsg_qos_handson_update`, we will set a QoS policy on a |DP|. Request-Offered QoS Policies ============================ Some QoS policies are “Request-Offered,” meaning that a |DW| offers a level of service, and a |DR| requests a level of service. If the |DW| offers a level of service that’s the same as or higher than the |DR| requests, the QoS policies are matching. If the |DR| requests a higher level of service than the |DW| is offering, the QoS policies are considered “incompatible,” and the |DW| will not send data to the |DR|. Request-Offered semantics are abbreviated with the term "RxO." One example of a Request-Offered QoS policy is Reliability. Reliable delivery is considered a higher level of service than best-effort delivery. Although a |DW| may offer reliable delivery, not every |DR| it’s communicating with needs reliability. Let’s look at an example of a system that tracks aircraft. Some |DRs|—such as those in the air traffic control application—need the aircraft locations reliably, meaning they cannot miss an update. Other |DRs|—such as those in an application that updates flight times for customers to view departures and arrivals—do not need to receive every flight position update. The radar application |DW| will send aircraft positions to all the |DRs| that need it, but not all of those |DRs| need it reliably. .. figure:: static/qos/rxo_configured.png :scale: 50 % :alt: Request-Offered Example :name: FigureRequestOffered :align: center The Customer Application's |DR| does not need flight location data reliably. The Radar Application's |DW| can send to both |DRs| because its Reliability QoS Policy is the same as or higher than theirs. Now imagine that the system in :numref:`FigureRequestOffered` is misconfigured, so the |DW| offers Best Effort data, which is a lower level of service. In this situation, the Air Traffic Control Application can’t receive data reliably, even though it is critical that it receive every update. This is an error in the configuration of the system, and |CONNEXT| treats it that way: the |DW| and |DR| of these applications will not communicate, and the |DR| and |DW| will instead be notified that they have incompatible QoS policies. .. figure:: static/qos/rxo_misconfigured.png :figwidth: 95 % :alt: Request-Offered Misconfiguration :name: FigureROMisconfig :align: center The |DW| is misconfigured to offer only Best Effort reliability, and now it does not match with the Reliable |DR|. The |DW| is still compatible with the Customer Application's |DR|. In previous modules, our applications have only been notified of data being available (see :ref:`section-gsg_intro_rcvdata`). In :ref:`section-gsg_qos_handson_notification`, we will update one of the applications to receive incompatible QoS notifications in addition to Data Available notifications. Not all Quality of Service policies have Request-Offered semantics. For example, the History QoS Policy that we will discuss below is not request-offered: |DWs| and |DRs| can have their own history settings, independent of each other; therefore, their History QoS policies do not need to match. You can check which QoS policies do and do not have request-offered semantics by looking at the RxO column in the :link_connext_qosref_man:`QoS Reference Guide <>`. Some Basic QoS Policies ======================= Reliability and History QoS Policies ------------------------------------ The Reliability QoS Policy and History QoS Policy work together to determine how reliably data gets sent. "Best Effort" Reliability ^^^^^^^^^^^^^^^^^^^^^^^^^ In :ref:`section-gsg_intro_datatypes`, we introduced one data flow pattern where the QoS setting isn’t Reliable, called “Streaming Sensor Data.” Remember that Streaming Sensor Data has these characteristics: - Usually sent rapidly - Usually sent periodically - When data is lost over the network, it is more important to get the next update than to wait for retransmission of the lost update Because of these characteristics, streaming sensor data is generally not configured to be reliable. It is configured with “Best Effort” reliability. It is one end of the spectrum of how reliably your data can be sent. .. figure:: static/qos/best_effort.png :scale: 50 % :alt: Best Effort Reliability :name: BestEffortReliability :align: center Best-Effort Reliability: Samples that the |DR| did not receive are not resent. .. list-table:: Reliability QoS Policy :name: ReliabilityQoS :widths: 20 30 60 :header-rows: 1 * - Reliability (RxO) - Level (Lowest to Highest) - Definition * - **kind** - BEST_EFFORT - Do not send data reliably. If samples are lost, they are not resent. * - - RELIABLE - Send data reliably. Resend samples lost on the network, depending on the History QoS Policy and Resource Limits QoS Policy settings. "Reliable" Reliability + "Keep All" History ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The other end of the spectrum is a pattern called "Event and Alarm Data." The typical characteristics of event and alarm data are: - May be sent rapidly - Sent intermittently - Important to see every update for events and alarms that occur Event and Alarm Data requires a level of reliability where all data is kept for reliable |DRs|. This means that |CONNEXT| will try to resend all data not received by existing |DRs|, and it will maintain a queue of data that has not been delivered to the |DRs|. It also means that a |DW| will not overwrite data in its queue until all |DRs| have acknowledged they received the previously sent data (or have gone offline). This level of reliability is set up using: - Reliability **kind** = RELIABLE - History **kind** = KEEP_ALL .. figure:: static/qos/keep_all.png :scale: 50 % :alt: Keep-All Reliability :name: KeepAllReliability :align: center Keep-All Reliability: Now there is a queue, and all samples are kept in the queue. Samples cannot be overwritten until they are received. (More details and caveats are explained in :link_connext_reliable_users_man:`Reliable Communications, in the RTI Connext DDS Core Libraries User's Manual <>`.) .. _section-gsg_reliable_keeplast: "Reliable" Reliability + "Keep Last" History ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There is one more reliability configuration in-between "Best Effort" and "Keep-All" called Keep-Last Reliability. Keep-Last Reliability is a configuration where the last N number of samples sent are reliably delivered. This allows the |DW| to overwrite older samples with newer samples, even if some |DRs| have not received those older samples. This configuration is set up using: - Reliability **kind** = RELIABLE - History **kind** = KEEP_LAST - History **depth** = Number of samples to keep for each instance .. figure:: static/qos/keep_last.png :scale: 50 % :alt: Keep-Last Reliability, Single Instance :name: KeepLastReliability :align: center Keep-Last Reliability (unkeyed or with a single instance): In this example, you have four slots open on the writer side to keep samples that have not yet been received by the reader. Imagine that the |DW| in :numref:`KeepLastReliability` rapidly writes 10 samples. Four of those samples will overwrite the four samples that are kept in the queue; the next four will overwrite those four, and so on. The |DR| might accept those samples as rapidly as the |DW| writes them—if not, some of them might be lost. There is one important feature of how the History **depth** works that makes it important for many design patterns: the History **depth** QoS setting is applied *per instance*. This means that you specify a History **depth** of N samples, and |CONNEXT| will reliably deliver the last N samples *of each instance* (for example, of each flight). .. figure:: static/qos/keep_last_keyed.png :scale: 50 % :alt: Keep-Last Reliability, Multiple Instances :name: KeepLastReliabilityKeyed :align: center Keep-Last Reliability (with multiple instances): A history of four samples **per instance** are available to be reliably delivered to a |DR|. We’ve been focused on the |DW|'s queue when talking about Keep-Last Reliability, but the behavior is the same for the |DR|: the |DR| queue keeps History **depth** samples for each instance, and when a new sample arrives, it is allowed to overwrite an existing sample in the |DR|'s queue for that instance. On the |DW| side, the History **depth** controls how many samples to keep around until all matching |DRs| have fully acknowledged the samples. There is another depth, **writer_depth**, in the Durability QoS Policy, that controls what subset of the historical samples to send to |DRs| that come late to the system. We'll learn more about that in :ref:`section-gsg_qos_durability`. .. list-table:: History QoS Policy :name: HistoryQoS :widths: 20 30 60 :header-rows: 1 * - History (Not RxO) - Value - Definition * - **kind** - KEEP_LAST - Keep the last **depth** number of samples per instance in the queue until they are reliably delivered * - - KEEP_ALL - Keep all samples (subject to Resource Limits that we will discuss next) until they are reliably delivered * - **depth** - - How many samples to keep per instance for reliable delivery if Keep Last is specified Summary ^^^^^^^ You have a range of reliability options using the Reliability and History QoS policies, for various data patterns: - Streaming data like “ChocolateTemperature” that does not need reliability at all - State data like “ChocolateLotState”, where |DRs| generally want to reliably receive the latest state and can accept missing some state updates when the state is changing rapidly - Event and Alarm data that needs guaranteed delivery of every sample .. figure:: static/qos/reliability_summary.png :scale: 50 % :alt: Reliability Range, Summary :name: ReliabilityRangeSummary :align: center A range of reliability options between Best Effort and Reliable. If a RELIABLE **kind** is selected, the History QoS Policy comes into play. Resource Limits QoS Policy -------------------------- Even in a system where you need strict Keep-All Reliability, there may be a limit to the number of samples that you want to keep in a |DW|'s or |DR|'s queue at one time, because of memory resource constraints. You may want to set a maximum number of samples allowed in a |DW| or |DR| queue so that it does not grow indefinitely. The Resource Limits QoS Policy contains several limits, but the one we will focus on is **max_samples**. This setting limits the total number of samples in a |DW|'s or |DR|'s queue across all instances. If a |DW| or |DR| has its History **kind** set to KEEP_ALL, it is not allowed to overwrite samples in its queue to make room for new samples. So what happens if a |DW| or |DR| exceeds its **max_samples** resource limit? The |DW| and |DR| handle this situation slightly differently. |DWs| with Keep-All Reliability handle hitting their Resource Limits by blocking the ``write()`` call, waiting for an empty slot in the queue as the |DRs| receive the reliable data. |DRs| with Keep-All Reliability handle hitting their Resource Limits by rejecting any samples that arrive when their queue is full, and notifying the application so that the application can call ``take()`` to remove samples from the queue. .. figure:: static/qos/resource_limit_dw.png :scale: 50 % :alt: DataWriter Resource Limit :name: DWResourceLimit :align: center |DW| View: The |DW|'s ``write()`` call will block if it hits its resource limits, and one or more |DRs| have not received all the data. If the ``write()`` call times out without being able to send data, ``write()`` will return or throw an error. If you’re interested in the details of the reliability protocol and how |DWs| handle non-responsive |DRs| without becoming blocked forever, see :link_connext_reliable_users_man:`Reliable Communications, in the RTI Connext DDS Core Libraries User's Manual <>`. .. figure:: static/qos/resource_limit_dr.png :scale: 50 % :alt: DataReader Resource Limit :name: DRResourceLimit :align: center |DR| view: The |DR| rejects samples if it has reached its resource limits. .. tabularcolumns:: |\X{35}{120}|\X{25}{120}|\X{60}{120}| .. list-table:: Resource Limits QoS Policy :name: ResourceLimitsQoS :widths: 20 30 60 :header-rows: 1 * - Resource Limits (Not RxO) - Value - Definition * - **max_samples** - - The maximum samples allowed in a |DW|'s or |DR|'s queue across all instances * - **max_instances** - - The maximum number of instances a |DW| can write or a |DR| can read * - **max_samples_per_instance** - - The maximum number of samples allowed for each instance in a |DW|'s or |DR|'s queue We've covered only a few resource limits here. For more information, see :link_connext_resource_users_man:`RESOURCE_LIMITS QosPolicy, in the RTI Connext DDS Core Libraries User's Manual <>`. .. _section-gsg_qos_durability: Durability QoS Policy --------------------- The Durability QoS Policy specifies whether data will be delivered to a |DR| that was not known to the |DW| at the time the data was written (also called a “late-joining” |DR|). Perhaps the |DR| wasn't there or hadn't been discovered at the time the samples were written. This QoS policy is used in multiple patterns, including the State Data pattern. One example of State Data is the ChocolateLotState data in the chocolate factory. Recall that the applications in our example update the state of the lot, and the Monitoring/Control application monitors that state. The typical characteristics of state data are: - Typically state data does not change rapidly and periodically (otherwise, it would be streaming data). - Applications monitoring state data would like to reliably receive that data. - Applications monitoring state data need to know the current state. It is more important to receive the current state than to receive every state update that has ever happened, even if that means missing some state updates. These requirements of the State Data pattern can be met by setting History **kind** to KEEP_LAST, Durability **kind** to TRANSIENT_LOCAL or higher, and Durability **writer_depth** to the number of samples you want delivered to late-joining |DRs|. Typically when an application needs state data, it wants to receive the current states as soon as it starts up. The one thing we’ve been missing in our example is that if an application with a |DR| starts up late, right now it doesn’t find out the current state of the chocolate lots in the system. This is especially obvious if you start the Monitoring/Control application before you start the Tempering Station application: .. only:: cpp98 or cpp11 .. code-block:: text $ ./tempering_application waiting for lot Processing lot #3 waiting for lot waiting for lot waiting for lot Processing lot #4 .. only:: csharp .. code-block:: text $ dotnet run --project TemperingApplication waiting for lot Processing lot #3 waiting for lot waiting for lot waiting for lot Processing lot #4 The Monitoring/Control application has sent lots #0-4 to the Tempering application—but the first thing the Tempering application sees is lot #3! It lost the notifications about all the previous lots. We will fix this in one of the hands-on exercises later in this module, by using the Durability QoS Policy together with the Reliability and History QoS policies. We'll use a QoS profile that will specify a higher Durability level than the default, so the Tempering application's |DR| receives ChocolateLotState updates that were written before it started. The lowest level of durability is “Volatile,” which means that historical data is not sent to any late-joining |DR|. ("Historical" data is any data sent by a |DW| before it discovers a |DR|.) "Volatile" is the default durability setting. The next level of durability is “Transient Local,” which means that historical data is automatically maintained in the |DW|’s queue, up to the **writer_depth**. By default, the Durability **writer_depth** is the same as the History **depth**, but you can set it to be a subset of the History **depth**, as shown in :numref:`DurabilityDepth`. .. figure:: static/qos/durability_depth.png :scale: 50 % :alt: Durability Depth :name: DurabilityDepth :align: center History **depth** determines how many samples to keep for reliability purposes (for example, for redelivering to |DRs| that haven’t acknowledged them yet). Durability **writer_depth** determines what subset of the History **depth** samples to deliver to late-joining |DRs|. Late-joining |DRs| that also use reliability and Transient Local durability are automatically sent historical data, up to the Durability **writer_depth**, when they discover the |DW|. "Transient Local" is the setting we will use in the hands-on exercise later in this module. This setting will ensure that the Tempering application receives notifications about all previous lots when it starts up late. Typically, a **writer_depth** of 1 is used in the State Data pattern: .. figure:: static/qos/keep_last_610.png :scale: 50 % :alt: Keep-Last Reliability, Depth of 1 :name: KeepLastReliabilityDepth1 :align: center State Data pattern using reliability with a writer depth of 1. The late-joining |DR| doesn't need to know all or several previous states, just the most recent state. In :numref:`KeepLastReliabilityDepth1`, the |DW| is updating the ChocolateLotState only when that state changes. In the real world, this may happen infrequently, depending on how long it takes the station to process a lot. If the ChocolateLotState |DW| was using Best Effort and the |DR| missed an update, the |DW| would not necessarily send new data right away—so the |DR| might not receive the state of the lot for a long time. With a Reliability **kind** of RELIABLE and a Durability **writer_depth** of 1 (as well as a TRANSIENT_LOCAL **kind** or higher :ref:`section-gsg_qos_durability`), the |DR| has at least the last available state. It may not be obvious why this is so powerful at first, but this is the basis for the State Data pattern and we'll see it at work in the last hands-on exercise in this module. The Durability QoS Policy is a Request-Offered policy. For example, if the |DR| requests "Transient Local" durability, but the |DW| is set to "Volatile" durability, the entities are not compatible and they will not communicate. .. tabularcolumns:: |\X{20}{120}|\X{40}{120}|\X{60}{120}| .. list-table:: Durability QoS Policy :name: DurabilityQoS :widths: 20 30 60 :header-rows: 1 * - Durability (RxO) - Value (Lowest to Highest) - Definition * - **kind** - VOLATILE - Do not save or deliver historical DDS samples. (Historical samples are samples that were written before the |DR| was discovered by the |DW|.) * - - TRANSIENT_LOCAL - Save and deliver historical DDS samples if the |DW| still exists. * - - TRANSIENT_DURABILITY - Save and deliver historical DDS samples using *RTI Persistence Service* to store samples in volatile memory. * - - PERSISTENT_DURABILITY - Save and deliver historical DDS samples using *RTI Persistence Service* to store samples in non-volatile memory. * - **writer_depth** - - How many samples are stored by the |DW| application for sending to late-joining |DRs| (|DRs| that are found after the DDS samples were initially written). Must be <= to the History **depth**. As you can see in the table above, Durability has two additional **kinds**: TRANSIENT and PERSISTENT. These levels of durability allow historical data to be available to late-joining |DRs| even if the original |DW| is no longer in the system (because it has been shut down, for example). These higher levels of durability require that data is also stored by *RTI Persistence Service*. These QoS settings are sometimes used as part of the Event and Alarm Data pattern in systems where you need to guarantee delivery of Events even if the |DW| no longer exists. They might also be used in a State Data pattern where state synchronization is important: for example, a |DW| changes the state of a system (writes the result of some process) and then ends, and the next application must read that state in order to continue working with it. .. tip:: To summarize: - The History QoS Policy controls how many samples to keep for reliable delivery. - The Resource Limits QoS Policy limits the total resources that can be used for samples in the |DW| and |DR| queues. - The Durability QoS Policy determines whether, and how many, of the most recent samples are delivered to late-joining |DRs|. Deadline QoS Policy ------------------- The Deadline QoS Policy is used for periodic and streaming data, and can be configured on both the |DW| and the |DR|. When the Deadline QoS Policy is set on a |DR|, it specifies that the |DR| expects to receive data within a particular time period, or else the application should be notified. When the Deadline QoS Policy is set on a |DW|, it specifies that the |DW| commits to writing data within a particular time period, or else the application will be notified. For example, if an application hangs instead of writing within the deadline period, |CONNEXT| will notify that application that it’s not fulfilling the quality of service it offered. We will look at how to receive these notifications in :ref:`section-gsg_qos_handson_notification`. The Deadline QoS Policy is a request-offered QoS: the |DW| must offer the same or shorter deadline than what the |DR| requests. Typically, the |DW| is configured with a shorter deadline than the |DR|, due to the possibility of network latency. .. figure:: static/qos/deadline.png :scale: 50 % :alt: Deadline QoS :name: DeadlineQoS :align: center Deadline QoS Policy: The |DW| commits to writing data within a particular time period; the |DR| expects to receive data within a particular time period. .. list-table:: Deadline QoS Policy :name: DeadlineQoSTable :widths: 20 30 60 :header-rows: 1 * - Deadline (RxO) - Value - Definition * - **period** - deadline time: |DW| deadline must be <= |DR| request - Time period in which a |DW| offers to write and a |DR| requests to receive periodic samples .. _section-gsg_qos_patterns_review: QoS Patterns Review ------------------- The design patterns you will use for your data are made up of combinations of QoS settings. Here is a review of the patterns we have covered: .. tabularcolumns:: |\X{15}{100}|\X{20}{100}|\X{20}{100}|\X{25}{100}|\X{20}{100}| .. list-table:: QoS Patterns :name: QoS Patterns :widths: 20 20 20 20 20 :header-rows: 1 * - Pattern - Reliability kind - History - Durability - Deadline period * - Streaming (Periodic) Data - BEST_EFFORT - - **kind**: N/A - **depth**: N/A - - **kind**: VOLATILE - **writer_depth**: N/A - Period in which you expect to send or receive periodic data * - Event/Alarm Data - RELIABLE - - **kind**: KEEP_ALL - **depth**: N/A - - **kind**: Various (see below) - **writer_depth**: Number of samples to keep in the queue to be delivered to late-joining |DRs| - N/A * - State Data - RELIABLE - - **kind**: KEEP_LAST - **depth**: Number of samples to keep in the queue to be reliably delivered - - **kind**: TRANSIENT_LOCAL - **writer_depth**: Number of samples to keep in the queue to be delivered to late-joining |DRs|. Typically 1 - N/A Event and Alarm Data may have various levels of Durability **kind**, depending on whether your system design requires Events and Alarms to be available to late-joining |DRs|, and whether Events and Alarms must be available even if the original |DW| is no longer in the system. The Resource Limits QoS Policy that we have discussed is mostly orthogonal to our design patterns: it is ultimately a limit on the maximum memory for samples and instances that your system should use, and that is part of your overall system design. Typically, you design your data flows first, such as Streaming (Perioidic) Data, and set your QoS policies based on that data pattern. Then you refine your QoS settings, overriding individual settings such as resource limits, to make your QoS configuration work for your system. QoS Profiles ============ QoS profiles are groupings of QoS settings defined in an XML file. |CONNEXT| provides many builtin QoS profiles that you can use as a starting point for your systems. Some of these profiles cover the patterns we have just discussed, such as the "Pattern.Streaming" profile we have used before. The pattern-based profiles start with "Pattern" in the name. There are also many that are made up of basic sets of QoS configurations that you can use to build your own patterns. For example, there are QoS profiles based on data flow characteristics such as "Generic.StrictReliable.HighThroughput," which is configured for high-throughput, strictly-reliable data. All of the available Builtin QoS profiles can be viewed in the file ``/resource/xml/BuiltinProfiles.documentationONLY.xml``. .. note:: As its name implies, the ``BuiltinProfiles.documentationONLY.xml`` file is only for viewing what the builtin profiles contain. You cannot modify the builtin profiles; however, you can create your own profile that inherits from a builtin profile. Then you can overwrite parts of the inherited profile with your own QoS settings. In :ref:`section-gsg_types_modify_stream` (in :ref:`section-gsg_intro_datatypes`), we already changed a QoS profile in the XML to inherit from a builtin QoS profile. In that exercise, though, our code loaded a profile implicitly because ``is_default_qos`` was set to true in the XML file. This is dangerous, because the default QoS profile may not have all of the settings you want (and some settings you *don't* want). In the next hands-on exercises, we'll review new QoS profiles that inherit from builtin profiles and override some values. We will change the code to load those QoS profiles, allowing our application to specify the profiles it wants to use for individual |DPs|, |DWs|, or |DRs|. .. note:: Using ``is_default_qos="true"`` is not a best practice in production applications. It’s a convenient way to get you started quickly, but in production applications you should explicitly specify which QoS profile to use, instead of relying on a default. .. _section-gsg_qos_handson_update: Hands-On 1: Update One QoS Profile in the Monitoring/Control Application ======================================================================== In this exercise, you are starting with applications in the repository where we have pre-configured only some of the QoS profiles. We will start by configuring some of the correct QoS profiles for the Monitoring/Control application, but we will intentionally leave one |DW| configured to use the default QoS profile in error. This will allow us to debug incompatible QoS profiles. We will do the following: - Hands-On 1: - Load the QoS file. - Set up a |DP| QoS profile, to better help you debug QoS profiles in *Admin Console*. (For more information on |DPs|, see :ref:`section-gsg_pub_sub_dp`.) - Set up the correct QoS profiles in the |DRs|, but not the |DW|. |br| |br| - Hands-On 2: Debug the incompatible QoS profiles in *Admin Console*. |br| |br| - Hands-On 3: Add code to help debug incompatible QoS profiles directly in one of the applications. |br| |br| - Hands-On 4: Change the Monitoring/Control |DW|’s QoS profile to the correct QoS profile, so now the |DWs| and |DRs| are compatible. .. .. only:: cpp11 In this exercise, you’ll be working in the directory ``5_basic_qos/c++11``. (See :ref:`section-gsg_clone`.) .. only:: cpp98 In this exercise, you’ll be working in the directory ``5_basic_qos/c++98``. (See :ref:`section-gsg_clone`.) .. only:: csharp In this exercise, you’ll be working in the directory ``5_basic_qos/csharp``, which was created when you cloned the getting started repository from GitHub in the first module, in :ref:`section-gsg_clone`. .. only:: cpp98 or cpp11 .. tip:: You don't need to run ``rtisetenv_`` like you did in the previous modules because the CMake script finds the |CONNEXT| installation directory and sets up the environment for you. #. .. only:: cpp98 or cpp11 Run CMake. .. note:: You must have CMake version 3.11 or higher. .. only:: cpp98 In the ``5_basic_qos/c++98`` directory, type the following, depending on your operating system: .. only:: cpp11 In the ``5_basic_qos/c++11`` directory, type the following, depending on your operating system: .. tabs:: .. group-tab:: Linux .. code-block:: console $ mkdir build $ cd build $ cmake .. Make sure "cmake" is in your path. If |CONNEXT| is not installed in the default location (see :ref:`section-gsg_paths`), specify ``cmake`` as follows: ``cmake -DCONNEXTDDS_DIR= ..``. .. group-tab:: macOS .. code-block:: console $ mkdir build $ cd build $ cmake .. Make sure "cmake" is in your path. If |CONNEXT| is not installed in the default location (see :ref:`section-gsg_paths`), specify ``cmake`` as follows: ``cmake -DCONNEXTDDS_DIR= ..``. .. group-tab:: Windows #. Enter the following commands: .. code-block:: doscon > mkdir build > cd build > "c:\program files\CMake\bin\cmake" --help Your path name may vary. Substitute ``"c:\program files\CMake\bin\cmake"`` with the correct path for your CMake installation. ``--help`` will list all the compilers you can generate project files for. Choose an installed compiler version, such as "Visual Studio 15 2017". |br| |br| #. From within the ``build`` directory, create the build files: - If you have a 64-bit Windows machine, add the option ``-A x64``. For example: .. code-block:: doscon > "c:\program files\CMake\bin\cmake" -G "Visual Studio 15 2017" -D CONNEXTDDS_ARCH=x64Win64VS2017 -A x64 .. - If you have a 32-bit Windows machine, add the option ``-A Win32``. For example: .. code-block:: doscon > "c:\program files\CMake\bin\cmake" -G "Visual Studio 15 2017" -D CONNEXTDDS_ARCH=i86Win32VS2017 -A Win32 .. .. note:: If you are using Visual Studio 2019, use "2017" in your architecture name. - If |CONNEXT| is not installed in the default location (see :ref:`section-gsg_paths`), add the option ``-DCONNEXTDDS_DIR=``. For example: .. code-block:: doscon > c:\program files\CMake\bin\cmake" -G "Visual Studio 15 2017" -DCONNEXTDDS_ARCH=x64Win64VS2017 -A x64 -DCONNEXTDDS_DIR= .. .. only:: csharp Run *RTI Code Generator* (*rtiddsgen*) to generate the C# code for the IDL file ``chocolate_factory.idl`` as explained in :ref:`section-gsg_keys_compile_apps`. |br| |br| #. Within the application, load the ``qos_profiles.xml`` file from the repository by performing the following steps: .. only:: cpp98 Open the ``monitoring_ctrl_application.cxx`` file and look for the comment: .. code-block:: C++ // Exercise #1.1: Load QoS file Add the following code after the comment: .. code-block:: C++ DDSDomainParticipantFactory *factory = DDSDomainParticipantFactory::get_instance(); DDS_DomainParticipantFactoryQos factoryQos; DDS_ReturnCode_t factory_retcode = factory->get_qos(factoryQos); if (factory_retcode != DDS_RETCODE_OK) { return shutdown(NULL, "get_qos error", EXIT_FAILURE); } const char *url_profiles[1] = { "qos_profiles.xml" }; factoryQos.profile.url_profile.from_array(url_profiles, 1); factory->set_qos(factoryQos); This code explicitly loads an XML QoS file named ``qos_profiles.xml`` that we have provided for you in the ``c++98`` directory, instead of relying on the file with a default name (``USER_QOS_PROFILES.xml``) as we did in :numref:`section-gsg_intro_datatypes` (:ref:`section-gsg_types_modify_stream`). .. only:: cpp11 Open the ``monitoring_ctrl_application.cxx`` file and look for the comment: .. code-block:: C++ // Exercise #1.1: Add QoS provider Add the following code after the comment: .. code-block:: C++ // Exercise #1.1: Add QoS provider // Loads the QoS from the qos_profiles.xml file. dds::core::QosProvider qos_provider("./qos_profiles.xml"); This code explicitly loads an XML QoS file named ``qos_profiles.xml`` that we have provided for you in the ``c++11`` directory, instead of relying on the file with a default name (``USER_QOS_PROFILES.xml``) as we did in :numref:`section-gsg_intro_datatypes` (:ref:`section-gsg_types_modify_stream`). .. only:: csharp Open the ``MonitoringCtrlApplication.cs`` file and look for the comment: .. code-block:: C# // Exercise #1.1: Add QoS provider Add the following code after the comment: .. code-block:: C# // Exercise #1.1: Add QoS provider // Loads the QoS from the qos_profiles.xml file. var qosProvider = new QosProvider("./qos_profiles.xml"); This code explicitly loads an XML QoS file named ``qos_profiles.xml`` that we have provided for you in the ``csharp`` directory, instead of relying on the file with a default name (``USER_QOS_PROFILES.xml``) as we did in :numref:`section-gsg_intro_datatypes` (:ref:`section-gsg_types_modify_stream`). .. tip:: QoS profile XML files can be modified and loaded without recompiling the application. #. Review the QoS profiles in the ``qos_profiles.xml`` file. This XML file contains the QoS profiles that will be used by your applications. The file defines a QoS library, "ChocolateFactoryLibrary," that contains the QoS profiles we will be using: .. code-block:: xml Take a look at the "TemperingApplication" and "MonitoringControlApplication" qos_profiles in the XML file: .. code-block:: xml TemperingAppParticipant MonitoringControlParticipant We haven’t talked about setting QoS profiles on the |DPs| yet–we will say more about this when we talk about discovery. For now, what’s important is that if you set the ``participant_name`` QoS setting, |CONNEXT| makes that name visible to other applications. Since |DPs| own all your |DWs| and |DRs|, giving your |DPs| unique names makes it easier to tell your |DWs| and |DRs| apart when you are debugging in *Admin Console*. These |DP| profiles are pretty simple. They inherit from the builtin QoS profile "Generic.Common" by specifying that as the base profile: .. code-block:: xml base_name="BuiltinQosLib::Generic.Common" Then these QoS profiles override the default |DP|'s name: .. code-block:: xml MonitoringControlParticipant In the next step, we will change the Monitoring/Control Application’s |DP| to use this QoS profile to help us debug in *Admin Console*. Now review the "ChocolateTemperatureProfile" Qos profile, which will be used to specify QoS policies for |DWs| and |DRs| in our applications: .. code-block:: xml This profile inherits from the "Pattern.Streaming" profile, which provides a basic pattern for |DW| and |DR| QoS profiles for streaming data, including the following: - Reliability **kind** = BEST_EFFORT - Deadline **period** = 4 seconds |br| |br| Remember you can review the details of the default profiles like "Pattern.Streaming" in ``/resource/xml/BuiltinProfiles.documentationONLY.xml``. Review the "ChocolateLotStateProfile" QoS profile, which will be used to specify QoS policies for |DWs| and |DRs| in our applications: .. code-block:: xml This profile inherits from the "Pattern.Status" profile, which provides the following |DW| and |DR| QoS settings: - Reliability **kind** = RELIABLE - Durability **kind** = TRANSIENT_LOCAL - History **kind** = KEEP_LAST - History **depth** = 1 - Durability **writer_depth** = AUTO_WRITER_DEPTH (i.e., inherited from the History **depth**, which is 1 in this example; AUTO_WRITER_DEPTH is the default setting for **writer_depth**) |br| |br| This is the State Data pattern described above, in :ref:`section-gsg_reliable_keeplast`. |br| |br| #. .. only:: cpp98 or cpp11 Add the |DP|'s QoS profile in ``monitoring_ctrl_application.cxx``. .. only:: csharp Add the |DP|'s QoS profile in ``MonitoringCtrlApplication.cs``. Look for the comment: .. code-block:: C++ // Exercise #1.2: Load DomainParticipant QoS profile .. only:: cpp98 Replace these lines: .. code-block:: C++ DDSDomainParticipant *participant = DDSTheParticipantFactory->create_participant( domain_id, DDS_PARTICIPANT_QOS_DEFAULT, NULL /* listener */, DDS_STATUS_MASK_NONE); So the code looks like this: .. code-block:: C++ // Exercise #1.2: Load DomainParticipant QoS profile DDSDomainParticipant *participant = DDSTheParticipantFactory->create_participant_with_profile( domain_id, "ChocolateFactoryLibrary", "MonitoringControlApplication", NULL /* listener */, DDS_STATUS_MASK_NONE); .. only:: cpp11 Replace this line: .. code-block:: C++ dds::domain::DomainParticipant participant(domain_id); So the code looks like this: .. code-block:: C++ // Exercise #1.2: Load DomainParticipant QoS profile dds::domain::DomainParticipant participant( domain_id, qos_provider.participant_qos( "ChocolateFactoryLibrary::MonitoringControlApplication")); .. only:: csharp Replace this line: .. code-block:: C# DomainParticipant participant = DomainParticipantFactory.Instance .CreateParticipant(domainId); So the code looks like this: .. code-block:: C# // Exercise #1.2: Load DomainParticipant QoS profile var participantQos = qosProvider.GetDomainParticipantQos( "ChocolateFactoryLibrary::MonitoringControlApplication"); DomainParticipant participant = DomainParticipantFactory.Instance .CreateParticipant(domainId, participantQos); With this code, you have just explicitly loaded the "MonitoringControlApplication" QoS profile, which is part of the ChocolateFactoryLibrary. Remember that this QoS profile inherits from the default |DP| QoS profile, but specifies the ``participant_name`` to be "MonitoringControlParticipant" in the QoS file. |br| |br| #. .. only:: cpp98 Add the |DRs|’ QoS profile in ``monitoring_ctrl_application.cxx``. Look for the comment: .. code-block:: C++ // Exercise #1.3: Update the lot_state_reader to use correct QoS .. only:: cpp11 Add the |DRs|’ QoS profile in ``monitoring_ctrl_application.cxx``. Look for the comment: .. code-block:: C++ // Exercise #1.3: Update the lot_state_reader and temperature_reader // to use correct QoS .. only:: csharp Add the |DRs|’ QoS profile in ``MonitoringCtrlApplication.cs``. Look for the comment: .. code-block:: C# // Exercise #1.3: Update the lotStateReader and temperatureReader // to use correct QoS Replace these lines: .. only:: cpp98 .. code-block:: C++ DDSDataReader *lot_state_generic_reader = subscriber->create_datareader( lot_state_topic, DDS_DATAREADER_QOS_DEFAULT, NULL, DDS_STATUS_MASK_NONE); .. only:: cpp11 .. code-block:: C++ dds::sub::DataReader lot_state_reader(subscriber, topic); // Add a DataReader for Temperature to this application dds::sub::DataReader temperature_reader( subscriber, temperature_topic); .. only:: csharp .. code-block:: C# DataReader lotStateReader = subscriber.CreateDataReader(lotStateTopic); // Add a DataReader for Temperature to this application DataReader temperatureReader = subscriber.CreateDataReader(temperatureTopic); So the code looks like this: .. only:: cpp98 .. code-block:: C++ // Exercise #1.3: Update the lot_state_reader to use correct QoS DDSDataReader *lot_state_generic_reader = subscriber->create_datareader_with_profile( lot_state_topic, "ChocolateFactoryLibrary", "ChocolateLotStateProfile", NULL, DDS_STATUS_MASK_NONE); .. only:: cpp11 .. code-block:: C++ // Exercise #1.3: Update the lot_state_reader and temperature_reader // to use correct QoS dds::sub::DataReader lot_state_reader( subscriber, topic, qos_provider.datareader_qos( "ChocolateFactoryLibrary::ChocolateLotStateProfile")); // Add a DataReader for Temperature to this application dds::sub::DataReader temperature_reader( subscriber, temperature_topic, qos_provider.datareader_qos( "ChocolateFactoryLibrary::ChocolateTemperatureProfile")); .. only:: csharp .. code-block:: C# // Exercise #1.3: Update the lot_state_reader and temperature_reader // to use correct QoS var readerQos = qosProvider.GetDataReaderQos( "ChocolateFactoryLibrary::ChocolateLotStateProfile"); DataReader lotStateReader = subscriber.CreateDataReader(lotStateTopic, readerQos); // Add a DataReader for Temperature to this application readerQos = qosProvider.GetDataReaderQos( "ChocolateFactoryLibrary::ChocolateTemperatureProfile"); DataReader temperatureReader = subscriber.CreateDataReader(filteredTemperatureTopic, readerQos); .. only:: cpp98 Now look for the comment: .. code-block:: C++ // Exercise #1.4: Update the lot_state_reader to use correct QoS Replace these lines: .. code-block:: C++ DDSDataReader *temperature_generic_reader = subscriber->create_datareader( temperature_topic, DDS_DATAREADER_QOS_DEFAULT, NULL, DDS_STATUS_MASK_NONE); So the code looks like this: .. code-block:: C++ // Exercise #1.4: Update the lot_state_reader to use correct QoS DDSDataReader *temperature_generic_reader = subscriber->create_datareader_with_profile( temperature_topic, "ChocolateFactoryLibrary", "ChocolateTemperatureProfile", NULL, DDS_STATUS_MASK_NONE); Now that you have added that code, the |DRs| are each loading the correct QoS profiles. You have not changed the |DW|, so it is erroneously using the default QoS profile. |br| |br| #. .. only:: cpp98 or cpp11 Compile and run the two applications (**tempering_application** and **monitoring_ctrl_application**): Compile the code: .. tabs:: .. group-tab:: Linux From within the ``build`` directory: .. code-block:: console $ make .. group-tab:: macOS From within the ``build`` directory: .. code-block:: console $ make .. group-tab:: Windows From within the ``build`` directory: #. Open ``rticonnextdds-getting-started-qos.sln`` in Visual Studio by entering ``rticonnextdds-getting-qos.sln`` at the command prompt or using File > Open Project in Visual Studio. |br| |br| #. Right-click ``ALL_BUILD`` and choose Build. (See ``2_hello_world\\README_.txt`` if you need more help.) Since "Debug" is the default option at the top of Visual Studio, a Debug directory will be created with the compiled files. Run the Monitoring/Control application: .. only:: cpp98 or cpp11 .. tabs:: .. group-tab:: Linux From within the ``build`` directory: .. code-block:: console $ ./monitoring_ctrl_application .. group-tab:: macOS From within the ``build`` directory: .. code-block:: console $ ./monitoring_ctrl_application .. group-tab:: Windows From within the ``build`` directory: .. code-block:: doscon > Debug\monitoring_ctrl_application.exe .. only:: csharp .. code-block:: console $ dotnet run --project MonitoringCtrlApplication In another command prompt window, run the Tempering application with a sensor ID as a parameter: .. only:: cpp98 or cpp11 .. tabs:: .. group-tab:: Linux From within the ``build`` directory: .. code-block:: console $ ./tempering_application -i 9 .. group-tab:: macOS From within the ``build`` directory: .. code-block:: console $ ./tempering_application -i 9 .. group-tab:: Windows From within the ``build`` directory: .. code-block:: doscon > Debug\tempering_application.exe -i 9 .. only:: csharp .. code-block:: console $ dotnet run --project TemperingApplication -- --sensor-id 9 .. note:: Make sure you run ``dotnet`` from ``5_basic_qos/csharp`` since that's where the applications expect to find ``qos_profiles.xml``. #. Look at the output of the applications: The Monitoring/Control application starts lots, but we never see lot updates: .. code-block:: text Starting lot: [lot_id: 0 next_station: StationKind::TEMPERING_CONTROLLER ] Starting lot: [lot_id: 1 next_station: StationKind::TEMPERING_CONTROLLER ] The Tempering application waits for lots, but it never gets any updates about lots: .. only:: cpp98 .. code-block:: text ChocolateTemperature Sensor with ID: 9 starting Waiting for lot Waiting for lot Wait timed out after 10 seconds. Waiting for lot Wait timed out after 10 seconds. Waiting for lot Wait timed out after 10 seconds. .. only:: cpp11 or csharp .. code-block:: text ChocolateTemperature Sensor with ID: 9 starting Waiting for lot Waiting for lot Waiting for lot Waiting for lot Waiting for lot The |DWs| and |DRs| are not communicating because they have incompatible QoS profiles, which we will debug in the next hands-on exercise. |br| |br| #. Keep the applications running for the next hands-on exercise. Hands-On 2: Incompatible QoS in Admin Console ============================================= As we mentioned above, the Tempering application is using all correct QoS settings, and we updated the |DRs| in the Monitoring/Control application. But we intentionally didn't update the Monitoring/Control application's |DW|. We will debug this problem using *Admin Console*. #. Make sure the **monitoring_ctrl_application** and **tempering_application** are still running. |br| |br| #. Open *Admin Console*. - Open *Admin Console* from *RTI Launcher*. |br| |br| - Choose the Administration view: .. figure:: static/qos/ac_adminvis_open.png :figwidth: 40 % :alt: Administration View :name: AdministrationView :align: center (The Administration view might be selected for you already, since we used that view in the previous module.) |br| |br| #. You should immediately notice that there is a red error showing on your "ChocolateLotState" *Topic*. .. figure:: static/qos/ac_topic_error.png :figwidth: 40 % :alt: Topic Error in Admin Console :name: TopicErrorAdminConsole :align: center If you hover over the ChocolateLotState *Topic*, you will see an error: "Request/offered QoS." |br| |br| #. Click on the "ChocolateLotState" *Topic* in the DDS Logical View, and you should see a diagram showing the |DWs| and |DRs| that are writing and reading the *Topic*. It's easy to see which application the |DWs| and |DRs| belong to, because you’ve set the ``participant_name`` QoS setting, which allows you to see the names of the |DPs| (**MonitoringControlParticipant** and **TemperingAppParticipant**) that own the |DWs| and |DRs| in this system. .. figure:: static/qos/ac_qos_mismatch.png :figwidth: 80 % :alt: QoS Mismatch in Admin Console :name: MismatchAdminConsole :align: center In the above view, some |DWs| and |DRs| are matching, while others are not. The |DW| that is configured correctly is green, the misconfigured one is red. The "ChocolateLotState" |DRs| in the system, in yellow, are partially matched: they match with the |DW| in the Tempering application that is configured correctly. Remember that the “ChocolateLotState” |DWs| and |DRs| look like :numref:`IncompatibleQoS` in our system; the Monitoring/Control application’s |DW| is not configured correctly: .. figure:: static/qos/multiple_dwdr_anno_xy.png :scale: 50 % :alt: Incompatible QoS :name: IncompatibleQoS :align: center Two |DWs| and two |DRs| should be communicating in our example system, when configured correctly. But the |DW| QoS policy in the Monitoring/Control application is not configured correctly. (See also :numref:`MultipleReadersWritersCrossApp`.) #. Click on the red |DW| for the Monitoring/Control application. At the bottom of *Admin Console*’s screen, click on the Match Analyses tab: .. figure:: static/qos/ac_match_analyses.png :figwidth: 90 % :alt: Match Analyses View, Admin Console :name: MatchAnalysesView :align: center Notice in the Offered and Requested columns that the misconfigured |DW| is offering Volatile durability (meaning that it will not save data for late-joining |DRs|). However, the two |DRs| are requesting Transient Local durability (meaning that they need data saved for when they join late—this is the correct setting, since it follows the State Data pattern). The |DR| is requesting a higher level of QoS setting ("I need data saved") than the |DW| is offering ("I'm not saving data"). This is a system error, caused by incompatible QoS policies. Scroll down in this view to see the second |DR| that is mismatched with the |DW|. .. tip:: Any time a |DR| requests a higher level of service than the |DW| offers, |CONNEXT| reports it as a system error. .. _section-gsg_qos_handson_notification: Hands-On 3: Incompatible QoS Notification ========================================= .. only:: cpp98 or cpp11 Both your |DWs| and |DRs| can be notified in your own applications when they discover a |DR| or |DW| with incompatible QoS settings. In the ``5_basic_qos`` directory, we have added a StatusCondition to the Tempering application’s |DR| to detect incompatible QoS policies. (This is a change to the Tempering application from the version used in the previous module, in ``4_keys_instances``.) In the steps below, you will review this additional code and add new code that prints out the error. .. only:: csharp Both your |DWs| and |DRs| can be notified in your own applications when they discover a |DR| or |DW| with incompatible QoS settings. In the ``5_basic_qos`` directory, we are monitoring the ``RequestedIncompatibleQos`` |DR| event to detect incompatible QoS policies. (This is a change to the Tempering application from the version used in the previous module, in ``4_keys_instances``.) In the steps below, you will review this additional code and add new code that prints out the error. #. Quit both of your applications if you haven't already. |br| |br| #. .. only:: cpp98 or cpp11 Open the ``tempering_application.cxx`` file. |br| |br| .. only:: csharp Open the ``TemperingApplication.cs`` file. |br| |br| #. .. only:: cpp98 Review the code. Notice that we have already added code to detect when the |DR| discovers a |DW| with an incompatible QoS policy. When this happens, it calls the function ``on_requested_incompatible_qos``: .. code-block:: C++ :emphasize-lines: 1,2,3,4,47,48,49 // Enable only the statuses we are interested in: // DDS_DATA_AVAILABLE_STATUS, DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS retcode = status_condition->set_enabled_statuses( DDS_DATA_AVAILABLE_STATUS | DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS); if (retcode != DDS_RETCODE_OK) { shutdown(participant, "set_enabled_statuses error", EXIT_FAILURE); } // Create the WaitSet and attach the Status Condition to it. The WaitSet // will be woken when the condition is triggered. DDSWaitSet waitset; retcode = waitset.attach_condition(status_condition); if (retcode != DDS_RETCODE_OK) { shutdown(participant, "attach_condition error", EXIT_FAILURE); } ... // Main loop, wait for lots // ------------------------ while (!shutdown_requested) { // Wait for ChocolateLotState std::cout << "Waiting for lot" << std::endl; // wait() blocks execution of the thread until one or more attached // Conditions become true, or until a user-specified timeout expires. DDSConditionSeq active_conditions_seq; DDS_Duration_t wait_timeout = { 10, 0 }; retcode = waitset.wait(active_conditions_seq, wait_timeout); // You get a timeout if no conditions were triggered before the timeout if (retcode == DDS_RETCODE_TIMEOUT) { std::cout << "Wait timed out after 10 seconds." << std::endl; continue; } else if (retcode != DDS_RETCODE_OK) { std::cerr << "wait returned error: " << retcode << std::endl; break; } // Get the status changes to check which status condition // triggered the WaitSet to wake DDS_StatusMask triggeredmask = lot_state_reader->get_status_changes(); // If the status is "Data Available" if (triggeredmask & DDS_DATA_AVAILABLE_STATUS) { process_lot(lot_state_reader, lot_state_writer); } if (triggeredmask & DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS) { on_requested_incompatible_qos(lot_state_reader); } .. only:: cpp11 Review the code. Notice that we have already added code to detect when the |DR| discovers a |DW| with an incompatible QoS policy. When this happens, it calls the function ``on_requested_incompatible_qos``: .. code-block:: C++ :emphasize-lines: 4,15,16,17,18,19 // Enable the 'data available' and 'requested incompatible qos' statuses reader_status_condition.enabled_statuses( dds::core::status::StatusMask::data_available() | dds::core::status::StatusMask::requested_incompatible_qos()); // Associate a handler with the status condition. This will run when the // condition is triggered, in the context of the dispatch call (see below) reader_status_condition.extensions().handler( [&lot_state_reader, &lot_state_writer]() { if((lot_state_reader.status_changes() & dds::core::status::StatusMask::data_available()) != dds::core::status::StatusMask::none()) { process_lot(lot_state_reader, lot_state_writer); } if ((lot_state_reader.status_changes() & dds::core::status::StatusMask::requested_incompatible_qos()) != dds::core::status::StatusMask::none()) { on_requested_incompatible_qos(lot_state_reader); } }); // Create a WaitSet and attach the StatusCondition dds::core::cond::WaitSet waitset; waitset += reader_status_condition; .. only:: csharp Review the code. Notice that we have already added code to detect when the |DR| discovers a |DW| with an incompatible QoS policy. When this happens, it calls the event handler ``OnRequestedIncompatibleQos``: .. code-block:: C# :emphasize-lines: 4 DataReader lotStateReader = subscriber.CreateDataReader( lotStateTopic, qos: qosProvider.GetDataReaderQos("ChocolateFactoryLibrary::ChocolateLotStateProfile"), preEnableAction: reader => reader.RequestedIncompatibleQos += OnRequestedIncompatibleQos); We configure the event handler in a ``preEnableAction`` to ensure that the event handler is installed before the |DR| is enabled and therefore we don't miss any status update. |br| |br| #. .. only:: cpp98 Find the ``on_requested_incompatible_qos`` function, and look for the comment: .. code-block:: C++ // Exercise #3.1 add a message to print when this DataReader discovers an // incompatible DataWriter Add the following code after the comment: .. code-block:: C++ std::cout << "Discovered DataWriter with incompatible policy: "; if (status.last_policy_id == DDS_RELIABILITY_QOS_POLICY_ID) { std::cout << "Reliability" << std::endl; } if (status.last_policy_id == DDS_DURABILITY_QOS_POLICY_ID) { std::cout << "Durability" << std::endl; } .. only:: cpp11 Find the ``on_requested_incompatible_qos`` function, and add code after the comment: .. code-block:: C++ // Exercise #3.1 add a message to print when this DataReader discovers an // incompatible DataWriter std::cout << "Discovered DataWriter with incompatible policy: "; if (incompatible_policy == policy_id::value) { std::cout << "Reliability"; } else if (incompatible_policy == policy_id::value) { std::cout << "Durability"; } std::cout << std::endl; .. only:: csharp Find the ``OnRequestedIncompatibleQos`` method, and add code after the comment: .. code-block:: C# // Exercise #3.1 add a message to print when this DataReader discovers an // incompatible DataWriter Type incompatiblePolicy = status.LastPolicy; Console.WriteLine( "Discovered DataWriter with incompatible policy: " + incompatiblePolicy.Name); The code you just added will print an error message when the |DR| discovers an incompatible |DW|. |br| |br| #. Rebuild and run both applications. When you run the Tempering application, you will now see notifications when it discovers |DWs| with incompatible QoS policies: .. code-block:: text ChocolateTemperature Sensor with ID: 33 starting waiting for lot Discovered DataWriter with incompatible policy: Durability waiting for lot waiting for lot (|CONNEXT| prints the error only the first time it notices the incompatibility.) |br| |br| #. Quit both applications. Hands-On 4: Using Correct QoS Profile ===================================== Finally, let’s go back to the Monitoring/Control application. This is where you added the QoS profile for the |DP| and |DRs| earlier. Now you will be modifying the |DW|’s QoS profile to use the correct values. #. .. only:: cpp98 or cpp11 Edit ``monitoring_ctrl_application.cxx``. .. only:: csharp Edit ``MonitoringCtrlApplication.cs``. Find the comment: .. code-block:: C++ // Exercise #4.1: Load ChocolateLotState DataWriter QoS profile after // debugging incompatible QoS .. only:: cpp98 Replace these lines: .. code-block:: C++ DDSDataWriter *generic_lot_state_writer = publisher->create_datawriter( lot_state_topic, DDS_DATAWRITER_QOS_DEFAULT, NULL /* listener */, DDS_STATUS_MASK_NONE); So the code looks like this: .. code-block:: C++ // Exercise #4.1: Load ChocolateLotState DataWriter QoS profile after // debugging incompatible QoS DDSDataWriter *generic_lot_state_writer = publisher->create_datawriter_with_profile( lot_state_topic, "ChocolateFactoryLibrary", "ChocolateLotStateProfile", NULL /* listener */, DDS_STATUS_MASK_NONE); .. only:: cpp11 Replace this line: .. code-block:: C++ dds::pub::DataWriter lot_state_writer(publisher, topic); So the code looks like this: .. code-block:: C++ // Exercise #4.1: Load ChocolateLotState DataWriter QoS profile after // debugging incompatible QoS dds::pub::DataWriter lot_state_writer( publisher, topic, qos_provider.datawriter_qos( "ChocolateFactoryLibrary::ChocolateLotStateProfile")); .. only:: csharp Replace this line: .. code-block:: C# DataWriter lotStateWriter = publisher.CreateDataWriter(lotStateTopic); So the code looks like this: .. code-block:: C# // Exercise #4.1: Load ChocolateLotState DataWriter QoS profile after // debugging incompatible QoS var writerQos = qosProvider.GetDataWriterQos( "ChocolateFactoryLibrary::ChocolateLotStateProfile"); DataWriter lotStateWriter = publisher.CreateDataWriter(lotStateTopic, writerQos); You have now configured the "ChocolateLotState" |DW| to use the “ChocolateLotStateProfile” QoS profile. Recall that this profile sets a Durability **kind** of TRANSIENT_LOCAL and an effective **writer_depth** of 1, so that late-joining |DRs| can get the latest data. |br| |br| #. Compile your applications and rerun. Now you see that they are communicating correctly, and the Tempering application always starts processing lot #0, even if the Tempering application started after the Monitoring/Control application. This is because the Tempering application is using the State Data pattern for its QoS profile: Reliability **kind** = RELIABLE, History **kind** = KEEP_LAST, Durability **kind** = TRANSIENT_LOCAL, Durability **writer_depth** = 1 (inherited from the History **depth** of 1). Recall that the Tempering application processes the lot and updates the status of each lot it receives. .. code-block:: text ChocolateTemperature Sensor with ID: 35 starting waiting for lot Processing lot #0 waiting for lot Processing lot #1 waiting for lot waiting for lot Processing lot #2 waiting for lot Recall that the Monitoring/Control application starts each lot at the TEMPERING CONTROLLER. The TEMPERING CONTROLLER then updates the lot state to indicate it is WAITING, PROCESSING, or completed. (The "current station" is invalid before the tempering controller starts processing the lot, because the Tempering application is the first station, so there is no station before that.) .. code-block:: text Starting lot: [lot_id: 0 next_station: StationKind::TEMPERING_CONTROLLER ] Received Lot Update: [lot_id: 0, station: StationKind::INVALID_CONTROLLER , next_station: StationKind::TEMPERING_CONTROLLER , lot_status: LotStatusKind::WAITING ] Received Lot Update: [lot_id: 0, station: StationKind::TEMPERING_CONTROLLER , next_station: StationKind::INVALID_CONTROLLER , lot_status: LotStatusKind::PROCESSING ] Received Lot Update: [lot_id: 0 is completed] Starting lot: [lot_id: 1 next_station: StationKind::TEMPERING_CONTROLLER ] Received Lot Update: [lot_id: 1, station: StationKind::INVALID_CONTROLLER , next_station: StationKind::TEMPERING_CONTROLLER , lot_status: LotStatusKind::WAITING ] Received Lot Update: [lot_id: 1, station: StationKind::TEMPERING_CONTROLLER , next_station: StationKind::INVALID_CONTROLLER , lot_status: LotStatusKind::PROCESSING ] Received Lot Update: [lot_id: 1 is completed] #. If you haven't already, start the Monitoring/Control application before the Tempering application and see that the Tempering application processes all of the lots, starting with 0, even though the Tempering application in this scenario is a late joiner. Upon startup, the Tempering application *gets a current view of your entire system*. This is because all of the QoS policies relevant to the State Data pattern are working together: since Reliability **kind** is RELIABLE and Durability **kind** is TRANSIENT_LOCAL, the Tempering application sees all the ChocolateLotState instances even if its |DR| is a late-joiner; since Durability **writer_depth** is 1, it sees one sample from each instance. Contrast this with our discussion in :ref:`section-gsg_qos_durability`, where the Monitoring/Control application sent lots #0-4, but the Tempering application saw only lot #3 at the start. This was because we hadn't set Durability yet. .. figure:: static/qos/depth1_startup.png :scale: 50 % :alt: Reliability, History, and Durability QoS :name: RelHistDuraQoS :align: center Reliability, History, and Durability QoS policies work together so that the late-joining |DR| sees one sample per instance. Next Steps ========== Congratulations! You have learned about several of the basic Quality of Service offered by |CONNEXT|. We only scratched the surface of the configuration options you have available, so for further information about QoS policies, you should look at the following documents: - :link_connext_qosref_man:`QoS Reference Guide <>` - :link_connext_dwqos_users_man:`DataWriter QoS, in the RTI Connext DDS Core Libraries User's Manual <>` - :link_connext_drqos_users_man:`DataReader QoS, in the RTI Connext DDS Core Libraries User's Manual <>` - :link_connext_qosxml_users_man:`Configuring QoS with XML, in the RTI Connext DDS Core Libraries User's Manual <>` In an upcoming module, we will be looking at filtering data using *ContentFilteredTopics*.