1414#include " config/configuration.h"
1515#include " model/timeout_clock.h"
1616
17+ #include < seastar/coroutine/as_future.hh>
18+
1719#include < exception>
1820#include < iterator>
1921#include < utility>
@@ -90,64 +92,67 @@ ss::future<> level_one_log_reader_impl::fetch_metadata(
9092 co_return ;
9193 }
9294
93- try {
94- auto response = co_await _metastore->get_first_ge (
95- _tidp, _config.start_offset );
96-
97- if (response.has_value ()) {
98- auto & obj = response.value ();
99-
100- vlog (
101- cd_log.debug ,
102- " Found L1 object {} for {} ({}) at offset {}" ,
103- obj.oid ,
104- _ntp,
105- _tidp,
106- _config.start_offset );
95+ auto response_fut = co_await ss::coroutine::as_future (
96+ _metastore->get_first_ge (_tidp, _config.start_offset ));
97+ if (response_fut.failed ()) {
98+ vlog (
99+ cd_log.error ,
100+ " Exception fetching metadata from metastore for {} ({}): {}" ,
101+ _ntp,
102+ _tidp,
103+ response_fut.get_exception ());
104+ _state = state::end_of_stream;
105+ co_return ;
106+ }
107107
108- auto footer_result = co_await read_footer (
109- obj.oid , obj.footer_pos , obj.object_size );
110- if (!footer_result.has_value ()) {
111- vlog (
112- cd_log.warn ,
113- " Failed to read footer from L1 object {} for {} ({}): {}" ,
114- obj.oid ,
115- _ntp,
116- _tidp,
117- std::to_underlying (footer_result.error ()));
118- _state = state::end_of_stream;
119- co_return ;
120- }
121- _current_obj = current_object{
122- .oid = obj.oid , .footer = std::move (footer_result.value ())};
123- _state = state::ready;
124- } else if (response.error () == l1::metastore::errc::out_of_range) {
108+ auto response = response_fut.get ();
109+ if (!response.has_value ()) {
110+ if (response.error () == l1::metastore::errc::out_of_range) {
125111 vlog (
126112 cd_log.debug ,
127113 " No L1 objects found for {} ({}) at offset {} or later" ,
128114 _ntp,
129115 _tidp,
130116 _config.start_offset );
131117 _state = state::end_of_stream;
132- } else {
133- vlog (
134- cd_log.warn ,
135- " Failed to query metastore for {} ({}) offset {}: {}" ,
136- _ntp,
137- _tidp,
138- _config.start_offset ,
139- response.error ());
140- _state = state::end_of_stream;
118+ co_return ;
141119 }
142- } catch (...) {
143120 vlog (
144- cd_log.error ,
145- " Failed to fetch metadata from metastore for {} ({}): {}" ,
121+ cd_log.warn ,
122+ " Failed to query metastore for {} ({}) offset {}: {}" ,
123+ _ntp,
124+ _tidp,
125+ _config.start_offset ,
126+ response.error ());
127+ _state = state::end_of_stream;
128+ co_return ;
129+ }
130+
131+ auto & obj = response.value ();
132+ vlog (
133+ cd_log.debug ,
134+ " Found L1 object {} for {} ({}) at offset {}" ,
135+ obj.oid ,
136+ _ntp,
137+ _tidp,
138+ _config.start_offset );
139+
140+ auto footer_result = co_await read_footer (
141+ obj.oid , obj.footer_pos , obj.object_size );
142+ if (!footer_result.has_value ()) {
143+ vlog (
144+ cd_log.warn ,
145+ " Failed to read footer from L1 object {} for {} ({}): {}" ,
146+ obj.oid ,
146147 _ntp,
147148 _tidp,
148- std::current_exception ( ));
149+ std::to_underlying (footer_result. error () ));
149150 _state = state::end_of_stream;
151+ co_return ;
150152 }
153+ _current_obj = current_object{
154+ .oid = obj.oid , .footer = std::move (footer_result.value ())};
155+ _state = state::ready;
151156}
152157
153158ss::future<std::expected<l1::footer, l1::io::errc>>
@@ -165,13 +170,41 @@ level_one_log_reader_impl::read_footer(
165170 auto * abort_source = _config.abort_source
166171 ? &_config.abort_source .value ().get ()
167172 : &default_abort_source;
168- auto stream_result = co_await _io->read_object (extent, abort_source);
173+ auto stream_fut = co_await ss::coroutine::as_future (
174+ _io->read_object (extent, abort_source));
175+ if (stream_fut.failed ()) {
176+ vlog (
177+ cd_log.error ,
178+ " Exception opening stream for footer from object {} (pos {} object "
179+ " size {}): {}" ,
180+ oid,
181+ extent.position ,
182+ object_size,
183+ stream_fut.get_exception ());
184+ co_return std::unexpected (l1::io::errc::file_io_error);
185+ }
186+ auto stream_result = stream_fut.get ();
169187 if (!stream_result.has_value ()) {
170188 co_return std::unexpected (stream_result.error ());
171189 }
172190
173191 auto & stream = stream_result.value ();
174- iobuf footer_buf = co_await read_iobuf_exactly (stream, footer_total_size);
192+
193+ auto footer_fut = co_await ss::coroutine::as_future (
194+ read_iobuf_exactly (stream, footer_total_size));
195+ if (footer_fut.failed ()) {
196+ vlog (
197+ cd_log.error ,
198+ " Exception reading footer from object {} (pos {} object size {}): {}" ,
199+ oid,
200+ extent.position ,
201+ object_size,
202+ footer_fut.get_exception ());
203+ co_await stream.close ();
204+ co_return std::unexpected (l1::io::errc::file_io_error);
205+ }
206+
207+ iobuf footer_buf = footer_fut.get ();
175208 co_await stream.close ();
176209
177210 // Parse the footer - we have the complete footer so this should succeed.
@@ -213,112 +246,121 @@ ss::future<> level_one_log_reader_impl::materialize_batches(
213246 _current_obj.has_value (),
214247 " Expected to have current object in ready state" );
215248
216- try {
217- size_t start_pos
218- = _current_obj->footer .file_position_before_kafka_offset (
219- _tidp, _config.start_offset );
220- if (start_pos == l1::footer::npos) {
221- // This shouldn't happen- the L1 metastore told us there's
222- // data for this NTP, and we checked after the latest consume
223- // that the object isn't exhausted of data.
224- vlog (
225- cd_log.error ,
226- " Reader in ready state but no data for {} ({}) in object {}" ,
227- _ntp,
228- _tidp,
229- _current_obj->oid );
230- _state = state::end_of_stream;
231- co_return ;
232- }
249+ size_t start_pos = _current_obj->footer .file_position_before_kafka_offset (
250+ _tidp, _config.start_offset );
251+ if (start_pos == l1::footer::npos) {
252+ // This shouldn't happen- the L1 metastore told us there's
253+ // data for this NTP, and we checked after the latest consume
254+ // that the object isn't exhausted of data.
255+ vlog (
256+ cd_log.error ,
257+ " Reader in ready state but no data for {} ({}) in object {}" ,
258+ _ntp,
259+ _tidp,
260+ _current_obj->oid );
261+ _state = state::end_of_stream;
262+ co_return ;
263+ }
233264
234- l1::object_extent extent{
235- .id = _current_obj->oid ,
236- .position = start_pos,
237- .size = L1_max_bytes_per_object_fetch,
238- };
239- ss::abort_source default_abort_source;
240- auto * abort_source = _config.abort_source
241- ? &_config.abort_source .value ().get ()
242- : &default_abort_source;
243- auto stream_result = co_await _io->read_object (extent, abort_source);
244- if (!stream_result.has_value ()) {
265+ l1::object_extent extent{
266+ .id = _current_obj->oid ,
267+ .position = start_pos,
268+ .size = L1_max_bytes_per_object_fetch,
269+ };
270+ ss::abort_source default_abort_source;
271+ auto * abort_source = _config.abort_source
272+ ? &_config.abort_source .value ().get ()
273+ : &default_abort_source;
274+ auto stream_fut = co_await ss::coroutine::as_future (
275+ _io->read_object (extent, abort_source));
276+ if (stream_fut.failed ()) {
277+ vlog (
278+ cd_log.error ,
279+ " Exception opening stream for L1 object {} for {} ({}): {}" ,
280+ _current_obj->oid ,
281+ _ntp,
282+ _tidp,
283+ stream_fut.get_exception ());
284+ _state = state::end_of_stream;
285+ co_return ;
286+ }
287+ auto stream_result = stream_fut.get ();
288+ if (!stream_result.has_value ()) {
289+ vlog (
290+ cd_log.warn ,
291+ " Failed to open stream for L1 object {} for {} ({}): {}" ,
292+ _current_obj->oid ,
293+ _ntp,
294+ _tidp,
295+ std::to_underlying (stream_result.error ()));
296+ _state = state::end_of_stream;
297+ co_return ;
298+ }
299+
300+ auto reader = l1::object_reader::create (std::move (stream_result).value ());
301+ while (true ) {
302+ auto result_fut = co_await ss::coroutine::as_future (
303+ reader->read_next ());
304+ if (result_fut.failed ()) {
245305 vlog (
246- cd_log.warn ,
247- " Failed to open stream for L1 object {} for {} ({}): {}" ,
306+ cd_log.error ,
307+ " Exception reading L1 object {} for {} ({}): {}" ,
248308 _current_obj->oid ,
249309 _ntp,
250310 _tidp,
251- std::to_underlying (stream_result.error ()));
311+ result_fut.get_exception ());
312+ co_await reader->close ();
252313 _state = state::end_of_stream;
253314 co_return ;
254315 }
255316
256- auto reader = l1::object_reader::create (
257- std::move (stream_result).value ());
258- while (true ) {
259- auto result = co_await reader->read_next ();
260-
261- if (std::holds_alternative<model::record_batch>(result)) {
262- auto batch = std::move (std::get<model::record_batch>(result));
263-
264- // Skip batches before our start offset.
265- if (
266- batch.last_offset ()
267- < kafka::offset_cast (_config.start_offset )) {
268- continue ;
269- }
270-
271- // Stop if we've reached our max offset.
272- if (
273- batch.base_offset ()
274- >= kafka::offset_cast (_config.max_offset )) {
275- break ;
276- }
277-
278- // Check if adding this batch would exceed our byte limit.
279- size_t batch_size = batch.size_bytes ();
280- if (
281- (_config.strict_max_bytes || _config.bytes_consumed > 0 )
282- && _config.bytes_consumed + batch_size >= _config.max_bytes ) {
283- _config.over_budget = true ;
284- break ;
285- }
286- _config.bytes_consumed += batch_size;
287-
288- // If we make it past all that, emit the batch.
289- _batches.push_back (std::move (batch));
290- } else if (std::holds_alternative<model::topic_id_partition>(
291- result)) {
292- // Partition marker. Done with this ntp's partition.
317+ auto result = result_fut.get ();
318+ if (std::holds_alternative<model::record_batch>(result)) {
319+ auto batch = std::move (std::get<model::record_batch>(result));
320+
321+ // Skip batches before our start offset.
322+ if (
323+ batch.last_offset () < kafka::offset_cast (_config.start_offset )) {
324+ continue ;
325+ }
326+
327+ // Stop if we've reached our max offset.
328+ if (batch.base_offset () >= kafka::offset_cast (_config.max_offset )) {
293329 break ;
294- } else if (
295- std::holds_alternative<l1::footer>(result)
296- || std::holds_alternative<l1::object_reader::eof>(result)) {
297- // End of data.
330+ }
331+
332+ // Check if adding this batch would exceed our byte limit.
333+ size_t batch_size = batch.size_bytes ();
334+ if (
335+ (_config.strict_max_bytes || _config.bytes_consumed > 0 )
336+ && _config.bytes_consumed + batch_size >= _config.max_bytes ) {
337+ _config.over_budget = true ;
298338 break ;
299339 }
340+ _config.bytes_consumed += batch_size;
341+
342+ // If we make it past all that, emit the batch.
343+ _batches.push_back (std::move (batch));
344+ } else if (std::holds_alternative<model::topic_id_partition>(result)) {
345+ // Partition marker. Done with this ntp's partition.
346+ break ;
347+ } else if (
348+ std::holds_alternative<l1::footer>(result)
349+ || std::holds_alternative<l1::object_reader::eof>(result)) {
350+ // End of data.
351+ break ;
300352 }
353+ }
301354
302- co_await reader->close ();
355+ co_await reader->close ();
303356
304- vlog (
305- cd_log.debug ,
306- " Materialized {} batches from L1 object {} for {} ({})" ,
307- _batches.size (),
308- _current_obj->oid ,
309- _ntp,
310- _tidp);
311- } catch (...) {
312- vlog (
313- cd_log.error ,
314- " Failed to materialize batches from L1 object {} for {} ({}): {}" ,
315- _current_obj->oid ,
316- _ntp,
317- _tidp,
318- std::current_exception ());
319- _state = state::end_of_stream;
320- co_return ;
321- }
357+ vlog (
358+ cd_log.debug ,
359+ " Materialized {} batches from L1 object {} for {} ({})" ,
360+ _batches.size (),
361+ _current_obj->oid ,
362+ _ntp,
363+ _tidp);
322364
323365 _state = _batches.empty () ? state::end_of_stream : state::materialized;
324366}
0 commit comments