40.8 Accessing and Managing Instances (Working with Keyed Data Types)

This section describes how instances work on DataReaders. This section applies only to data types that use keys; see Chapter 8 DDS Samples, Instances, and Keys. See also Chapter 19 Working with Instances.

A DataReader receives updates about instances and instance state changes as DATA_AVAILABLE statuses, the same way it receives data updates. (See 40.7.1 DATA_AVAILABLE Status.) DataReaders can access instance state as part of the SampleInfo that is returned when calling any variant of read() or take() (such as read_instance() or take_instance()). See 41.6 The SampleInfo Structure.

40.8.1 Instance States

Connext keeps an instance_state for each instance. See Chapter 19 Working with Instances for a basic description of the instance states ALIVE, NOT_ALIVE_DISPOSED, and NOT_ALIVE_NO_WRITERS.

Instances can cycle through these phases as seen in the state diagram below, becoming NOT_ALIVE and then becoming ALIVE again. To track these transitions, there is metadata the DataReader can query called generation counts. (See 40.8.2 Generation Counts and Ranks.)

The events that cause the instance_state to change can depend on the setting of the 47.17 OWNERSHIP QosPolicy:

  • If OWNERSHIP QoS is set to EXCLUSIVE, the instance_state becomes NOT_ALIVE_DISPOSED only if the DataWriter that currently “owns” the instance explicitly disposes it. The instance_state will become ALIVE again only if the DataWriter that owns the instance writes it. Note that ownership of the instance is determined by a combination of the OWNERSHIP QoSPolicy and 47.18 OWNERSHIP_STRENGTH QosPolicy. Ownership of an instance can dynamically change.
  • If OWNERSHIP QoS is set to SHARED, the instance_state becomes NOT_ALIVE_DISPOSED if any DataWriter explicitly disposes the instance. The instance_state becomes ALIVE as soon as any DataWriter writes the instance again.

Figure 40.3: Instance States and Generation Counts

Since the instance_state in the SampleInfo structure is a per-instance concept, all DDS data samples related to the same instance that are returned by read() or take() will have the same value for instance_state. This means that if there are samples for that instance in the DataReader’s queue that were received when the instance was ALIVE, and a subsequent dispose message is received, the samples’ metadata will indicate that the instance’s state is NOT_ALIVE_DISPOSED in all of them.

Note: The instance_state always reflects the current state of the instance at the time of reading.

Figure 40.4: Before and After Dispose Received

When the dispose message is received (the box with the X, with valid_data = false), all samples for the flight 265 instance in the queue are marked as NOT_ALIVE_DISPOSED, even those that contain live data from when the instance was ALIVE.

In Figure 40.4: Before and After Dispose Received, imagine that, while the valid_data=false sample is in the queue, an instance transitions to ALIVE due to the reception of a data sample. The invalid sample will be removed from the DataReader queue because it is no longer needed to indicate the previous state transition. The new sample with valid data will indicate that the instance is now ALIVE. The generation count can then be queried to reflect that the instance had previously been in a state other than ALIVE (see 40.8.2 Generation Counts and Ranks for more information).

40.8.2 Generation Counts and Ranks

Generation counts and ranks allow your application to distinguish DDS samples belonging to different ‘generations’ of the instance. It is possible for an instance to become alive, be disposed and become not-alive, and then cycle again from alive to not-alive states during the operation of an application. Each time an instance becomes alive defines a new generation for the instance.

It is possible that an instance may cycle through alive and not-alive states multiple times before the application accesses the DDS data samples for the instance. This means that the DDS data samples returned by read() and take() may cross generations. That is, some DDS samples were published when the instance was alive in one generation and other DDS samples were published when the instance transitioned through the non-alive state into the alive state again. It may be important to your application to distinguish the DDS data samples by the generation in which they were published.

Each DataReader keeps two counters for each instance it detects (recall that instances are distinguished by their key values):

  • disposed_generation_count: Counts how many times the instance_state of the corresponding instance changes from NOT_ALIVE_DISPOSED to ALIVE.
  • no_writers_generation_count: Counts how many times the instance_state of the corresponding instance changes from NOT_ALIVE_NO_WRITERS to ALIVE.

The disposed_generation_count and no_writers_generation_count fields in the SampleInfo structure capture a snapshot of the corresponding counters at the time the corresponding DDS sample was received.

The sample_rank and generation_rank in the SampleInfo structure are computed relative to the sequence of DDS samples returned by read() or take():

  • sample_rank: Indicates how many DDS samples of the same instance follow the current one in the sequence. The DDS samples are always time-ordered, thus the newest DDS sample of an instance will have a sample_rank of 0. Depending on what you have configured read() and take() to return (by passing in state masks and through the max_samples_per_read field in 48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension)), a sample_rank of 0 may or may not be the newest DDS sample that was ever received. It is just the newest DDS sample in the sequence that was returned. The sample_rank value could be used by an application to determine that there are newer samples in the sequence and that it might want to skip processing the older samples.
  • generation_rank: Indicates the difference in ‘generations’ between the DDS sample and the newest DDS sample of the same instance as returned in the sequence. If a DDS sample belongs to the same generation as the newest DDS sample in the sequence returned by read() and take(), then generation_rank will be 0.
  • absolute_generation_rank: Indicates the difference in ‘generations’ between the DDS sample and the newest DDS sample of the same instance ever received by the DataReader. Recall that the data sequence returned by read() and take() may not contain all of the data in the DataReader’s receive queue. Thus, a DDS sample that belongs to the newest generation of the instance will have an absolute_generation_rank of 0.

By using the sample_rank, generation_rank and absolute_generation_rank information in the SampleInfo structure, your application can determine exactly what happened to the instance and thus make appropriate decisions of what to do with the DDS data samples received for the instance. For example:

  • A DDS sample with sample_rank= 0 is the newest DDS sample of the instance in the returned sequence.
  • DDS samples that belong to the same generation will have the same generation_rank (as well as absolute_generation_rank).
  • DDS samples with absolute_generation_rank = 0 belong to the newest generation for the instance received by the DataReader.

The ‘generation count’ and ‘rank’ values are statistics that are locally generated by each DataReader and maintained as part of the metadata for the instance that they refer to. Therefore, if the instance is reclaimed and then returns at a later point in time, these counters will all restart at 0.

40.8.3 Valid Data Flag

The SampleInfo structure’s valid_data flag indicates whether the DDS sample contains data or is only used to communicate a change in the instance_state of the instance.

Normally, each DDS sample contains both a SampleInfo structure and some data. However, there are situations in which the DDS sample only contains the SampleInfo and does not have any associated data. This occurs when Connext notifies the application of a change of state for an instance for which there is no associated data. An example is when Connext detects that an instance has no writers and changes the corresponding instance_state to NOT_ALIVE_NO_WRITERS.

If the valid_data flag is TRUE, then the DDS sample contains valid data. If the flag is FALSE, the DDS sample contains no data.

To ensure correctness and portability, your application must check the valid_data flag prior to accessing the data associated with the DDS sample, and only access the data if it is TRUE. The value of data is undefined when the valid_data flag is false.

40.8.4 Looking up an Instance Handle

Some operations, such as read_instance(), require an instance_handle parameter. If you need to get such a handle, you can call the FooDataReader’s lookup_instance() operation, which takes a sample with key fields specified as a parameter and returns a handle to that instance.

DDS_InstanceHandle_t lookup_instance (const Foo & key_holder)

The instance must have been received by the DataReader in order for the DataReader to look it up. If the instance is not known to the DataReader, this operation returns DDS_HANDLE_NIL.

40.8.5 Getting the Key Value for an Instance

Once you have an instance handle (using lookup_instance(), as part of a status change notification, or through the SampleInfo), you can use the DataReader’s get_key_value() operation to retrieve the value of the key of the corresponding instance. The key fields of the data structure passed into get_key_value() will be filled out with the original values used to generate the instance handle. The key fields are defined when the data type is defined; see Chapter 8 DDS Samples, Instances, and Keys for more information.

If you set propagate_dispose_of_unregistered_instances to true and wish to call get_key_value() for instances for which only a dispose sample has been received, the serialize_key_with_dispose field in the 47.5 DATA_WRITER_PROTOCOL QosPolicy (DDS Extension) must be set to true.

40.8.6 Instance Resource Limits and Memory Management

In Connext, memory is primarily pre-allocated when creating entities. When data is keyed, the memory associated with each instance used for storing instance-specific metadata is allocated when the DataReader is created. Memory is not freed at runtime, unless you delete an entity. Instead, memory is made available to be reused by the DataReader, or “reclaimed”.

The DataReader can receive a number of instances defined by the 47.22 RESOURCE_LIMITS QosPolicy and 48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension). It is also important to understand that an instance in the DataReader queue has two parts that make up the instance metadata: an active state and a minimum state. The resource limits control the amount of active state and minimum state that should be maintained. (Note: the concept of active and minimum state does not apply to instance metadata in the DataWriter queue.)

40.8.7 Active State and Minimum State

An instance is considered either attached or detached in the DataReader queue and is composed of two parts, which make up the instance metadata: an active state and a minimum state.

Figure 40.5: Active and Minimum Instance States

An instance is considered attached when the DataReader is actively managing all possible state that can be associated with an instance, including the associated samples, the instance and view states, generation and sample ranks, the list of remote writers that are known to be writing the instance, and so on. Only attached instances can have associated samples. A DataReader keeps both the active and the minimum state for attached instances. The sum of the alive_instance_count, disposed_instance_count, and no_writers_instance_count statistics in the 40.7.2 DATA_READER_CACHE_STATUS reflects the total number of attached instances currently in the DataReader queue.

The following is applicable only if keep_minimum_state_for_instances in the 48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension) is TRUE (by default, it is). See 48.2.2 keep_minimum_state_for_instances for more on this QoS setting.

An instance is considered detached when the DataReader is only maintaining the minimum state for the instance. When instances are replaced or purged from the DataReader queue, by default only the active state of the instance is reclaimed. A minimum amount of state for the instance is kept even after the instance is removed in order to maintain system consistency without having to waste resources (memory and CPU) by keeping other information around that is no longer relevant (i.e., the active state).

The minimum state is used when instances that have been removed re-enter the system. This can happen, for example, when a non-VOLATILE DataReader and DataWriter lose liveliness and then re-discover each other. The DataWriter will resend its history, but if the DataReader has the minimum state information for any instances that it removed during the disconnection, the previously received duplicate samples will be filtered out and dropped before being accepted into the DataReader’s queue again.

The minimum state includes information such as the last source timestamp, the serialized key, the keyhash, and the list of virtual writers for the instance. It also includes the last known state of the instance, which will be used when a DataWriter regains liveliness, if you are using RECOVER_INSTANCE_STATE_CONSISTENCY. See 19.1.5 Transition after NOT_ALIVE_NO_WRITERS.

In general, you should keep keep_minimum_state_for_instances set to true if you are using the Durable Reader State, MultiChannel DataWriters, or RTI Persistence Service, or in any system where instances may be removed and then re-enter the system either because the original DataWriter is re-discovered or writes the instance again or a new DataWriter begins writing the instance.

An instance transitions from what is considered an attached instance to a detached instance when the instance is removed from the DataReader queue (purged or replaced). This can happen under the following conditions:

  • The instance is replaced due to the instance_replacement settings in the 48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension).
  • There are no more samples associated with the instance. Samples can be removed from the DataReader queue through the use of the take() operation, or various QoS configurations such as a finite lifespan or KEEP_LAST history configuration. In addition, at least one of the following must be true:
    • The instance was in the NOT_ALIVE_NO_WRITERS instance state and autopurge_nowriter_instances_delay has expired. (The default value for the autopurge_nowriter_instances_delay is 0, so by default instances are purged as soon as the instance is empty and transitions to NOT_ALIVE_NO_WRITERS.)
    • The instance was in the NOT_ALIVE_DISPOSED instance state and the autopurge_disposed_instances_delay has expired.

The detached_instance_count statistic in the 40.7.2 DATA_READER_CACHE_STATUS counts the total number of detached instances currently in the DataReader queue.

40.8.8 Instance Resource Limit QoS Policies

The 47.22 RESOURCE_LIMITS QosPolicy and 48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension) include the following fields that affect the number of instances that can be received:

  • max_instances (47.22 RESOURCE_LIMITS QosPolicy): A resource limit on the number of attached instances that can be managed by Connext. By default, max_instances is UNLIMITED, so you are bounded only by the physical resources of your system. If the max_instances limit has been hit, and a sample is received for a new instance, Connext will first attempt to replace an instance according to what you have configured in the instance_replacement field in the 48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension). If there are not any replaceable instances (by default empty NOT_ALIVE_DISPOSED and NOT_ALIVE_NO_WRITERS instances are replaceable, and ALIVE instances are not replaceable), the sample will be lost with the reason LOST_BY_INSTANCES_LIMIT, and not re-sent by the DataWriter. The sum of the alive_instance_count, disposed_instance_count, and no_writers_instance_count statistics in the 40.7.2 DATA_READER_CACHE_STATUS reflects the total number of attached instances currently in the DataReader queue.
  • max_total_instances (48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension)): A resource limit on the combined total number of attached+detached instances that can be managed by Connext. This resource limit limits the number of minimum instance states that can be kept by the middleware, and both attached and detached instances require the minimum instance state to be kept. The detached_instance_count statistic in the 40.7.2 DATA_READER_CACHE_STATUS counts the total number of detached instances currently in the DataReader queue.
    • When a DataReader receives a new instance, Connext will check max_instances. If max_instances is not exceeded, Connext will check max_total_instances. If max_total_instances is exceeded, Connext will replace one of the detached instances with the new, attached one. The application could receive duplicate samples for the replaced instance if it becomes alive again.
    • max_total_instances should be equal to the number of attached instances you want to keep, plus the number of detached instances you want to keep.
  • keep_minimum_state_for_instances (48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension)): This QoS setting can be used to enable or disable Connext from keeping minimum instance information for detached instances. By default, this setting is TRUE. This minimum instance information is useful for the features described earlier in this section. If this QoS setting is FALSE, minimum instance state will not be kept, and therefore detached instances will not be kept.

The instance_replacement field in the 48.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension) controls whether instances can be replaced to make room for new ones. See 48.2.3 Configuring DataReader Instance Replacement.

The 48.3 READER_DATA_LIFECYCLE QoS Policy controls whether the DataReader can remove data from the queue if instance state becomes NOT_ALIVE_NO_WRITERS or NOT_ALIVE_DISPOSED.