Coverage Report

Created: 2026-07-25 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/Fast-DDS/src/cpp/fastdds/publisher/DataWriterHistory.cpp
Line
Count
Source
1
// Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
 * @file DataWriterHistory.cpp
17
 */
18
#include <fastdds/publisher/DataWriterHistory.hpp>
19
20
#include <chrono>
21
#include <limits>
22
#include <mutex>
23
24
#include <fastdds/dds/common/InstanceHandle.hpp>
25
#include <fastdds/dds/log/Log.hpp>
26
#include <fastdds/dds/topic/qos/TopicQos.hpp>
27
#include <fastdds/rtps/common/Time_t.hpp>
28
#include <fastdds/rtps/writer/RTPSWriter.hpp>
29
30
#include <rtps/history/HistoryAttributesExtension.hpp>
31
#include <rtps/writer/BaseWriter.hpp>
32
33
namespace eprosima {
34
namespace fastdds {
35
namespace dds {
36
37
using namespace eprosima::fastdds::rtps;
38
39
HistoryAttributes DataWriterHistory::to_history_attributes(
40
        const HistoryQosPolicy& history_qos,
41
        const ResourceLimitsQosPolicy& resource_limits_qos,
42
        const rtps::TopicKind_t& topic_kind,
43
        uint32_t payloadMaxSize,
44
        MemoryManagementPolicy_t mempolicy)
45
0
{
46
0
    auto initial_samples = resource_limits_qos.allocated_samples;
47
0
    auto max_samples = resource_limits_qos.max_samples;
48
0
    auto extra_samples = resource_limits_qos.extra_samples;
49
50
0
    if (history_qos.kind != KEEP_ALL_HISTORY_QOS)
51
0
    {
52
0
        max_samples = history_qos.depth;
53
0
        if (topic_kind != NO_KEY)
54
0
        {
55
0
            if (0 < resource_limits_qos.max_instances)
56
0
            {
57
0
                max_samples *= resource_limits_qos.max_instances;
58
0
            }
59
0
            else
60
0
            {
61
0
                max_samples = LENGTH_UNLIMITED;
62
0
            }
63
0
        }
64
65
0
        initial_samples = get_min_max_samples(initial_samples, max_samples);
66
0
    }
67
68
0
    return HistoryAttributes(mempolicy, payloadMaxSize, initial_samples, max_samples, extra_samples);
69
0
}
70
71
DataWriterHistory::DataWriterHistory(
72
        const std::shared_ptr<IPayloadPool>& payload_pool,
73
        const std::shared_ptr<IChangePool>& change_pool,
74
        const HistoryQosPolicy& history_qos,
75
        const ResourceLimitsQosPolicy& resource_limits_qos,
76
        const rtps::TopicKind_t& topic_kind,
77
        uint32_t payloadMaxSize,
78
        MemoryManagementPolicy_t mempolicy,
79
        std::function<void (const fastdds::rtps::InstanceHandle_t&)> unack_sample_remove_functor)
80
0
    : WriterHistory(to_history_attributes(history_qos, resource_limits_qos, topic_kind, payloadMaxSize,
81
0
            mempolicy), payload_pool, change_pool)
82
0
    , history_qos_(history_qos)
83
0
    , resource_limited_qos_(resource_limits_qos)
84
0
    , topic_kind_(topic_kind)
85
0
    , unacknowledged_sample_removed_functor_(unack_sample_remove_functor)
86
0
{
87
0
    if (resource_limited_qos_.max_samples <= 0)
88
0
    {
89
0
        resource_limited_qos_.max_samples = std::numeric_limits<int32_t>::max();
90
0
    }
91
92
0
    if (resource_limited_qos_.max_instances <= 0)
93
0
    {
94
0
        resource_limited_qos_.max_instances = std::numeric_limits<int32_t>::max();
95
0
    }
96
97
0
    if (resource_limited_qos_.max_samples_per_instance <= 0)
98
0
    {
99
0
        resource_limited_qos_.max_samples_per_instance = std::numeric_limits<int32_t>::max();
100
0
    }
101
0
}
102
103
DataWriterHistory::~DataWriterHistory()
104
0
{
105
0
}
106
107
void DataWriterHistory::rebuild_instances()
108
0
{
109
0
    if (topic_kind_ == WITH_KEY)
110
0
    {
111
0
        for (CacheChange_t* change : m_changes)
112
0
        {
113
0
            t_m_Inst_Caches::iterator vit;
114
0
            if (find_or_add_key(change->instanceHandle, change->serializedPayload, &vit))
115
0
            {
116
0
                vit->second.cache_changes.push_back(change);
117
0
            }
118
0
        }
119
0
    }
120
0
}
121
122
bool DataWriterHistory::register_instance(
123
        const InstanceHandle_t& instance_handle,
124
        std::unique_lock<RecursiveTimedMutex>&,
125
        const std::chrono::time_point<std::chrono::steady_clock>&,
126
        SerializedPayload_t*& payload)
127
0
{
128
0
    payload = nullptr;
129
130
    /// Preconditions
131
0
    if (topic_kind_ == NO_KEY)
132
0
    {
133
0
        return false;
134
0
    }
135
136
0
    t_m_Inst_Caches::iterator vit;
137
0
    bool result = find_or_add_key(instance_handle, {}, &vit);
138
0
    if (result)
139
0
    {
140
0
        payload = &vit->second.key_payload;
141
0
    }
142
0
    return result;
143
0
}
144
145
fastdds::rtps::SerializedPayload_t* DataWriterHistory::get_key_value(
146
        const fastdds::rtps::InstanceHandle_t& handle)
147
0
{
148
0
    t_m_Inst_Caches::iterator vit = keyed_changes_.find(handle);
149
0
    if (vit != keyed_changes_.end() && vit->second.is_registered())
150
0
    {
151
0
        return &vit->second.key_payload;
152
0
    }
153
0
    return nullptr;
154
0
}
155
156
bool DataWriterHistory::prepare_change(
157
        CacheChange_t* change,
158
        std::unique_lock<RecursiveTimedMutex>& lock,
159
        const std::chrono::time_point<std::chrono::steady_clock>& max_blocking_time)
160
0
{
161
0
    if (m_isHistoryFull)
162
0
    {
163
0
        bool ret = false;
164
0
        bool is_acked = change_is_acked_or_fully_delivered(m_changes.front());
165
0
        InstanceHandle_t instance = topic_kind_ == NO_KEY ?
166
0
                HANDLE_NIL : m_changes.front()->instanceHandle;
167
168
0
        if (history_qos_.kind == KEEP_ALL_HISTORY_QOS)
169
0
        {
170
0
            ret = this->mp_writer->try_remove_change(max_blocking_time, lock);
171
            // If change was removed (ret == 1) in KeepAllHistory, it must have been acked
172
0
            is_acked = ret;
173
0
        }
174
0
        else if (history_qos_.kind == KEEP_LAST_HISTORY_QOS)
175
0
        {
176
0
            ret = this->remove_min_change(max_blocking_time);
177
0
        }
178
179
        // Notify if change has been removed unacknowledged
180
0
        if (ret && !is_acked)
181
0
        {
182
0
            unacknowledged_sample_removed_functor_(instance);
183
0
        }
184
0
        else if (!ret)
185
0
        {
186
0
            EPROSIMA_LOG_WARNING(RTPS_HISTORY,
187
0
                    "Attempting to add Data to Full WriterCache.");
188
0
            return false;
189
0
        }
190
0
    }
191
192
0
    assert(!m_isHistoryFull);
193
194
    // For NO_KEY we can directly add the change
195
0
    bool add = (topic_kind_ == NO_KEY);
196
0
    if (topic_kind_ == WITH_KEY)
197
0
    {
198
0
        t_m_Inst_Caches::iterator vit;
199
200
        // For WITH_KEY, we take into account the limits on the instance
201
        // In case we wait for a sequence to be acknowledged, we try several times
202
        // until we reach the max blocking timepoint
203
0
        while (!add)
204
0
        {
205
            // We should have the instance
206
0
            if (!find_or_add_key(change->instanceHandle, change->serializedPayload, &vit))
207
0
            {
208
0
                break;
209
0
            }
210
211
0
            if (history_qos_.kind == KEEP_LAST_HISTORY_QOS)
212
0
            {
213
0
                if (vit->second.cache_changes.size() < static_cast<size_t>(history_qos_.depth))
214
0
                {
215
0
                    add = true;
216
0
                }
217
0
                else
218
0
                {
219
0
                    bool is_acked = change_is_acked_or_fully_delivered(vit->second.cache_changes.front());
220
0
                    InstanceHandle_t instance = change->instanceHandle;
221
0
                    add = remove_change_pub(vit->second.cache_changes.front());
222
                    // Notify if removed unacknowledged
223
0
                    if (add && !is_acked)
224
0
                    {
225
0
                        unacknowledged_sample_removed_functor_(instance);
226
0
                    }
227
0
                }
228
0
            }
229
0
            else if (history_qos_.kind == KEEP_ALL_HISTORY_QOS)
230
0
            {
231
0
                if (vit->second.cache_changes.size() <
232
0
                        static_cast<size_t>(resource_limited_qos_.max_samples_per_instance))
233
0
                {
234
0
                    add = true;
235
0
                }
236
0
                else
237
0
                {
238
0
                    SequenceNumber_t seq_to_remove = vit->second.cache_changes.front()->sequenceNumber;
239
0
                    if (!mp_writer->wait_for_acknowledgement(seq_to_remove, max_blocking_time, lock))
240
0
                    {
241
                        // Timeout waiting. Will not add change to history.
242
0
                        break;
243
0
                    }
244
245
                    // vit may have been invalidated
246
0
                    if (!find_or_add_key(change->instanceHandle, change->serializedPayload, &vit))
247
0
                    {
248
0
                        break;
249
0
                    }
250
251
                    // If the change we were trying to remove was already removed, try again
252
0
                    if (vit->second.cache_changes.empty() ||
253
0
                            vit->second.cache_changes.front()->sequenceNumber != seq_to_remove)
254
0
                    {
255
0
                        continue;
256
0
                    }
257
258
                    // Remove change if still present
259
0
                    add = remove_change_pub(vit->second.cache_changes.front());
260
0
                }
261
0
            }
262
0
        }
263
264
0
        if (add)
265
0
        {
266
0
            vit->second.cache_changes.push_back(change);
267
0
        }
268
0
    }
269
270
0
    return add;
271
0
}
272
273
bool DataWriterHistory::add_pub_change(
274
        CacheChange_t* change,
275
        WriteParams& wparams,
276
        std::unique_lock<RecursiveTimedMutex>& lock,
277
        const std::chrono::time_point<std::chrono::steady_clock>& max_blocking_time)
278
0
{
279
0
    bool returnedValue = false;
280
0
    bool add = prepare_change(change, lock, max_blocking_time);
281
282
0
    if (add)
283
0
    {
284
#if HAVE_STRICT_REALTIME
285
        if (this->add_change_(change, wparams, max_blocking_time))
286
#else
287
0
        if (this->add_change_(change, wparams))
288
0
#endif // if HAVE_STRICT_REALTIME
289
0
        {
290
0
            EPROSIMA_LOG_INFO(RTPS_HISTORY,
291
0
                    " Change " << change->sequenceNumber << " added with key: " << change->instanceHandle
292
0
                               << " and " << change->serializedPayload.length << " bytes");
293
0
            returnedValue = true;
294
0
        }
295
0
    }
296
297
0
    return returnedValue;
298
0
}
299
300
bool DataWriterHistory::find_or_add_key(
301
        const InstanceHandle_t& instance_handle,
302
        const SerializedPayload_t& payload,
303
        t_m_Inst_Caches::iterator* vit_out)
304
0
{
305
0
    static_cast<void>(payload);
306
307
0
    t_m_Inst_Caches::iterator vit;
308
0
    vit = keyed_changes_.find(instance_handle);
309
0
    if (vit != keyed_changes_.end())
310
0
    {
311
0
        *vit_out = vit;
312
0
        return true;
313
0
    }
314
315
0
    if (static_cast<int>(keyed_changes_.size()) < resource_limited_qos_.max_instances)
316
0
    {
317
0
        vit = keyed_changes_.insert(std::make_pair(instance_handle, detail::DataWriterInstance())).first;
318
0
        vit->second.key_payload.copy(&payload, false);
319
0
        *vit_out = vit;
320
0
        return true;
321
0
    }
322
323
0
    return false;
324
0
}
325
326
bool DataWriterHistory::removeAllChange(
327
        size_t* removed)
328
0
{
329
330
0
    size_t rem = 0;
331
0
    std::lock_guard<RecursiveTimedMutex> guard(*this->mp_mutex);
332
333
0
    while (m_changes.size() > 0)
334
0
    {
335
0
        if (remove_change_pub(m_changes.front()))
336
0
        {
337
0
            ++rem;
338
0
        }
339
0
        else
340
0
        {
341
0
            break;
342
0
        }
343
0
    }
344
0
    if (removed != nullptr)
345
0
    {
346
0
        *removed = rem;
347
0
    }
348
0
    if (rem > 0)
349
0
    {
350
0
        return true;
351
0
    }
352
0
    return false;
353
0
}
354
355
bool DataWriterHistory::removeMinChange()
356
0
{
357
0
    if (mp_writer == nullptr || mp_mutex == nullptr)
358
0
    {
359
0
        EPROSIMA_LOG_ERROR(RTPS_HISTORY, "You need to create a Writer with this History before using it");
360
0
        return false;
361
0
    }
362
363
0
    std::lock_guard<RecursiveTimedMutex> guard(*this->mp_mutex);
364
0
    if (m_changes.size() > 0)
365
0
    {
366
0
        return remove_change_pub(m_changes.front());
367
0
    }
368
0
    return false;
369
0
}
370
371
bool DataWriterHistory::remove_change_pub(
372
        CacheChange_t* change)
373
0
{
374
0
    return DataWriterHistory::remove_change_pub(change, std::chrono::steady_clock::now() + std::chrono::hours(24));
375
0
}
376
377
bool DataWriterHistory::remove_change_pub(
378
        CacheChange_t* change,
379
        const std::chrono::time_point<std::chrono::steady_clock>& max_blocking_time)
380
0
{
381
0
    if (mp_writer == nullptr || mp_mutex == nullptr)
382
0
    {
383
0
        EPROSIMA_LOG_ERROR(RTPS_HISTORY, "You need to create a Writer with this History before using it");
384
0
        return false;
385
0
    }
386
387
#if HAVE_STRICT_REALTIME
388
    std::unique_lock<RecursiveTimedMutex> lock(*this->mp_mutex, std::defer_lock);
389
    if (!lock.try_lock_until(max_blocking_time))
390
    {
391
        EPROSIMA_LOG_ERROR(PUBLISHER, "Cannot lock the DataWriterHistory mutex");
392
        return false;
393
    }
394
#else
395
0
    std::lock_guard<RecursiveTimedMutex> guard(*this->mp_mutex);
396
0
#endif // if HAVE_STRICT_REALTIME
397
398
0
    if (topic_kind_ == NO_KEY)
399
0
    {
400
0
        if (remove_change(change, max_blocking_time))
401
0
        {
402
0
            m_isHistoryFull = false;
403
0
            return true;
404
0
        }
405
406
0
        return false;
407
0
    }
408
0
    else
409
0
    {
410
0
        t_m_Inst_Caches::iterator vit;
411
0
        vit = keyed_changes_.find(change->instanceHandle);
412
0
        if (vit == keyed_changes_.end())
413
0
        {
414
0
            return false;
415
0
        }
416
417
0
        for (auto chit = vit->second.cache_changes.begin(); chit != vit->second.cache_changes.end(); ++chit)
418
0
        {
419
0
            if (((*chit)->sequenceNumber == change->sequenceNumber) && ((*chit)->writerGUID == change->writerGUID))
420
0
            {
421
0
                if (remove_change(change, max_blocking_time))
422
0
                {
423
0
                    vit->second.cache_changes.erase(chit);
424
0
                    m_isHistoryFull = false;
425
0
                    return true;
426
0
                }
427
0
            }
428
0
        }
429
0
        EPROSIMA_LOG_ERROR(PUBLISHER, "Change not found, something is wrong");
430
0
    }
431
0
    return false;
432
0
}
433
434
bool DataWriterHistory::remove_change_g(
435
        CacheChange_t* a_change)
436
0
{
437
0
    return remove_change_pub(a_change, std::chrono::steady_clock::now() + std::chrono::hours(24));
438
0
}
439
440
bool DataWriterHistory::remove_change_g(
441
        CacheChange_t* a_change,
442
        const std::chrono::time_point<std::chrono::steady_clock>& max_blocking_time)
443
0
{
444
0
    return remove_change_pub(a_change, max_blocking_time);
445
0
}
446
447
bool DataWriterHistory::remove_instance_changes(
448
        const InstanceHandle_t& handle,
449
        const SequenceNumber_t& seq_up_to)
450
0
{
451
0
    if (mp_writer == nullptr || mp_mutex == nullptr)
452
0
    {
453
0
        EPROSIMA_LOG_ERROR(RTPS_HISTORY, "You need to create a Writer with this History before using it");
454
0
        return false;
455
0
    }
456
457
0
    if (topic_kind_ == NO_KEY)
458
0
    {
459
0
        EPROSIMA_LOG_ERROR(RTPS_HISTORY, "Cannot be removed instance changes of a NO_KEY DataType");
460
0
        return false;
461
0
    }
462
463
0
    std::lock_guard<RecursiveTimedMutex> guard(*this->mp_mutex);
464
0
    t_m_Inst_Caches::iterator vit;
465
0
    vit = keyed_changes_.find(handle);
466
0
    if (vit == keyed_changes_.end())
467
0
    {
468
0
        return false;
469
0
    }
470
471
0
    auto chit = vit->second.cache_changes.begin();
472
473
0
    for (; chit != vit->second.cache_changes.end() && (*chit)->sequenceNumber <= seq_up_to; ++chit)
474
0
    {
475
0
        if (remove_change(*chit))
476
0
        {
477
0
            m_isHistoryFull = false;
478
0
        }
479
0
    }
480
481
0
    vit->second.cache_changes.erase(vit->second.cache_changes.begin(), chit);
482
483
0
    if (vit->second.cache_changes.empty())
484
0
    {
485
0
        keyed_changes_.erase(vit);
486
0
    }
487
488
0
    return true;
489
0
}
490
491
bool DataWriterHistory::set_next_deadline(
492
        const InstanceHandle_t& handle,
493
        const std::chrono::steady_clock::time_point& next_deadline_us)
494
0
{
495
0
    if (mp_writer == nullptr || mp_mutex == nullptr)
496
0
    {
497
0
        EPROSIMA_LOG_ERROR(RTPS_HISTORY, "You need to create a Writer with this History before using it");
498
0
        return false;
499
0
    }
500
0
    std::lock_guard<RecursiveTimedMutex> guard(*this->mp_mutex);
501
502
0
    if (topic_kind_ == NO_KEY)
503
0
    {
504
0
        next_deadline_us_ = next_deadline_us;
505
0
        return true;
506
0
    }
507
0
    else if (topic_kind_ == WITH_KEY)
508
0
    {
509
0
        if (keyed_changes_.find(handle) == keyed_changes_.end())
510
0
        {
511
0
            return false;
512
0
        }
513
514
0
        keyed_changes_[handle].next_deadline_us = next_deadline_us;
515
0
        return true;
516
0
    }
517
518
0
    return false;
519
0
}
520
521
bool DataWriterHistory::get_next_deadline(
522
        InstanceHandle_t& handle,
523
        std::chrono::steady_clock::time_point& next_deadline_us)
524
0
{
525
0
    if (mp_writer == nullptr || mp_mutex == nullptr)
526
0
    {
527
0
        EPROSIMA_LOG_ERROR(RTPS_HISTORY, "You need to create a Writer with this History before using it");
528
0
        return false;
529
0
    }
530
0
    std::lock_guard<RecursiveTimedMutex> guard(*this->mp_mutex);
531
532
0
    if (topic_kind_ == WITH_KEY)
533
0
    {
534
0
        auto min = std::min_element(
535
0
            keyed_changes_.begin(),
536
0
            keyed_changes_.end(),
537
0
            [](
538
0
                const t_m_Inst_Caches::value_type& lhs,
539
0
                const t_m_Inst_Caches::value_type& rhs)
540
0
            {
541
0
                return lhs.second.next_deadline_us < rhs.second.next_deadline_us;
542
0
            });
543
544
0
        handle = min->first;
545
0
        next_deadline_us = min->second.next_deadline_us;
546
0
        return true;
547
0
    }
548
0
    else if (topic_kind_ == NO_KEY)
549
0
    {
550
0
        next_deadline_us = next_deadline_us_;
551
0
        return true;
552
0
    }
553
554
0
    return false;
555
0
}
556
557
bool DataWriterHistory::is_key_registered(
558
        const InstanceHandle_t& handle)
559
0
{
560
0
    if (mp_writer == nullptr || mp_mutex == nullptr)
561
0
    {
562
0
        EPROSIMA_LOG_ERROR(RTPS_HISTORY, "You need to create a Writer with this History before using it");
563
0
        return false;
564
0
    }
565
0
    std::lock_guard<RecursiveTimedMutex> guard(*this->mp_mutex);
566
0
    t_m_Inst_Caches::iterator vit;
567
0
    vit = keyed_changes_.find(handle);
568
0
    return vit != keyed_changes_.end() && vit->second.is_registered();
569
0
}
570
571
bool DataWriterHistory::wait_for_acknowledgement_last_change(
572
        const InstanceHandle_t& handle,
573
        std::unique_lock<RecursiveTimedMutex>& lock,
574
        const std::chrono::time_point<std::chrono::steady_clock>& max_blocking_time)
575
0
{
576
0
    if (WITH_KEY == topic_kind_)
577
0
    {
578
        // Find the instance
579
0
        t_m_Inst_Caches::iterator vit = keyed_changes_.find(handle);
580
0
        if (vit != keyed_changes_.end())
581
0
        {
582
0
            SequenceNumber_t seq = vit->second.cache_changes.back()->sequenceNumber;
583
0
            return mp_writer->wait_for_acknowledgement(seq, max_blocking_time, lock);
584
0
        }
585
0
    }
586
0
    return false;
587
0
}
588
589
bool DataWriterHistory::change_is_acked_or_fully_delivered(
590
        const CacheChange_t* change)
591
0
{
592
0
    bool is_acked = false;
593
0
    if (mp_writer->get_disable_positive_acks())
594
0
    {
595
0
        is_acked = mp_writer->has_been_fully_delivered(change->sequenceNumber);
596
0
    }
597
0
    else
598
0
    {
599
0
        is_acked = mp_writer->is_acked_by_all(change->sequenceNumber);
600
0
    }
601
0
    return is_acked;
602
0
}
603
604
}  // namespace dds
605
}  // namespace fastdds
606
}  // namespace eprosima