VLink  2.1.0
A high-performance communication middleware
bag_processor.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file bag_processor.h
26  * @brief Data-plane-time sliding-window reorder buffer, a helper for @c BagPluginInterface plugins.
27  *
28  * @details
29  * @c BagProcessor is the building block a @c BagPluginInterface plugin uses to reorder frames by
30  * their @e true data-plane time before re-emitting them -- on the write side before a frame is
31  * persisted, on the read side before it is replayed. It is symmetric: the same engine serves both
32  * directions because both push frames in and forward the reordered output through @c do_callback().
33  *
34  * Two distinct times are at play, which can disagree (out-of-order recording, async I/O, sender-side
35  * batching, transport requeue):
36  *
37  * - @c Frame::timestamp -- the canonical record / playback time the frame carries downstream.
38  * - the @b data-plane time -- the true event time carried inside the payload header, passed @e
39  * separately as the @c push() reorder key. @c BagProcessor emits frames in ascending data-plane-time
40  * order and remaps @c Frame::timestamp onto that sorted data-plane-time axis.
41  *
42  * @c BagProcessor is a @e pure reorder buffer. The plugin extracts the data-plane time from the
43  * header (parsing or deserialising the payload as needed) and passes it to @c push(); @c BagProcessor
44  * never inspects the payload nor touches the serialisation layer. Any payload transform (compress /
45  * decompress, URL / serialisation-type rewrite) is likewise the plugin's job, applied before @c push()
46  * or inside the output callback before it forwards the frame.
47  *
48  * The oldest cached frame is released only once the data-plane-time span between the oldest and
49  * newest cached frames reaches @c Config::min_cache_time, giving a late-but-earlier frame a chance to
50  * slot ahead of already-cached later frames. Before the first valid data-plane timestamp, the canonical
51  * @c Frame::timestamp advances the window and orders missing-time frames so streams without embedded
52  * timestamps remain bounded. Producer wall time never advances this window; a silent producer's tail is
53  * released by @c flush(). Data-plane timestamps passed to @c push() are in microseconds; @c Config time
54  * tunables are expressed in milliseconds and converted internally.
55  *
56  * @par Write-side example (reorder by header time, then persist)
57  * @code
58  * class MyWritePlugin : public vlink::BagPluginInterface {
59  * public:
60  * MyWritePlugin() {
61  * processor_.register_output_callback([this](const vlink::Frame& f) { do_callback(f); });
62  * }
63  *
64  * void on_read(const vlink::Frame& frame) override { do_callback(frame); } // unused on the write side
65  *
66  * void on_write(const vlink::Frame& frame) override {
67  * const int64_t data_timestamp = parse_header_time(frame.data); // plugin extracts the data-plane time
68  * processor_.push(data_timestamp, frame); // buffered + reordered by data_timestamp
69  * }
70  *
71  * void flush() override { processor_.flush(); } // drain the buffered tail at teardown
72  *
73  * private:
74  * vlink::BagProcessor processor_;
75  * };
76  * @endcode
77  */
78 
79 #pragma once
80 
81 #include <cstdint>
82 #include <memory>
83 #include <mutex>
84 
85 #include "../impl/types.h"
86 
87 namespace vlink {
88 
89 /**
90  * @class BagProcessor
91  * @brief Time-sorted relay buffer keyed on the data-plane time, shared by read- and write-side plugins.
92  *
93  * @details
94  * Thread-safe in the sense that @c push() may be called concurrently from the host's loop thread(s);
95  * delivery to the @c OutputCallback happens on a dedicated worker thread owned by the processor.
96  */
98  public:
99  /**
100  * @brief Sink receiving one @c Frame in data-plane-time order on the worker thread.
101  *
102  * @details
103  * Invoked once @c Config::min_cache_time of data-plane time has accumulated ahead of the candidate
104  * frame, the size limit forces progress, or @c flush() drains the tail. The frame is moved out of
105  * the cache into the callback.
106  * The callback frame timestamp is remapped onto the sorted data-plane-time axis so it stays strictly
107  * increasing after the reorder.
108  */
110 
111  /**
112  * @struct Config
113  * @brief Tunables controlling the reorder buffer behaviour.
114  */
115  struct Config final {
116  int64_t min_cache_time{500}; ///< Data-plane-time span to accumulate, in milliseconds.
117  int64_t max_cache_size{1024LL * 1024LL * 256}; ///< Maximum total payload bytes held (default 256 MiB).
118  int64_t max_jump_time{60LL * 60LL * 1000LL}; ///< Max absolute data-plane-time jump, in milliseconds.
119 
120  Config() {} // NOLINT(modernize-use-equals-default)
121  };
122 
123  /**
124  * @brief Builds the processor.
125  *
126  * @param config Cache time-window and memory-budget tunables.
127  */
128  explicit BagProcessor(const Config& config = Config());
129 
130  /**
131  * @brief Drains remaining frames in data-plane-time order and joins the worker thread.
132  */
134 
135  /**
136  * @brief Sets the sink receiving reordered frames and starts the worker thread.
137  *
138  * @details
139  * The first registration before @c push() takes effect; later registrations are ignored.
140  *
141  * @param output_callback Sink invoked once per frame on the worker thread.
142  */
143  void register_output_callback(OutputCallback&& output_callback);
144 
145  /**
146  * @brief Inserts a frame into the data-plane-time-sorted cache.
147  *
148  * @details
149  * Safe to call from any thread after @c register_output_callback(). Frames are ordered by
150  * @p data_timestamp. Negative @p data_timestamp is filled from the previous data timestamp and the
151  * @c Frame::timestamp delta; without a previous anchor it stays -1, sorts before valid data timestamps,
152  * and is ordered against other missing-time frames by @c Frame::timestamp. Output frame timestamps are
153  * remapped on the data-time axis and kept strictly increasing within a flush segment (@c flush() resets
154  * the axis). The frame is copied into the owning cache.
155  *
156  * @param data_timestamp Reorder key in microseconds (the data-plane time), or negative when missing.
157  * @param frame Frame to cache; @c Frame::timestamp is the canonical time before remapping.
158  */
159  void push(int64_t data_timestamp, const Frame& frame);
160 
161  /**
162  * @brief Synchronously drains every currently-buffered frame to the sink, in data-plane-time order.
163  *
164  * @details
165  * Blocks until the cache is empty: the worker thread emits each queued frame through the registered
166  * output callback, then wakes the caller. A plugin forwards this from @c BagPluginInterface::flush()
167  * so buffered tail frames are emitted before teardown. Draining also resets the timestamp-resolution
168  * anchors, so the next @c push() starts a fresh timeline -- @c flush() marks a file boundary, letting one
169  * processor instance serve consecutive bag files. Do not @c push() concurrently with @c flush(): frames
170  * racing the boundary belong to no defined timeline. A no-op before callback registration or shutdown.
171  */
172  void flush();
173 
174  /**
175  * @brief Synchronously discards every buffered frame and starts a fresh timestamp timeline.
176  *
177  * @details
178  * Waits for any output callback already in progress, clears the cache without emitting it, and resets
179  * all data-time resolution and output timestamp anchors. Read-side plugins use this at the start of a
180  * new playback session so frames retained by an interrupted stop or jump cannot enter the next timeline.
181  * Do not call this from the processor's output callback or concurrently with @c push(). A no-op before
182  * callback registration or during shutdown.
183  */
184  void reset();
185 
186  private:
187  bool on_check();
188 
189  void on_output(std::unique_lock<std::mutex>& lock, bool at_end);
190 
191  void on_run();
192 
193  void on_exec(bool at_end);
194 
195  void reset_timeline();
196 
197  struct Impl;
198  std::unique_ptr<Impl> impl_;
199 
201 };
202 
203 } // namespace vlink
#define VLINK_EXPORT
Definition: macros.h:81
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174