|
12 | 12 | #include <aws/io/tls_channel_handler.h> |
13 | 13 | #include <aws/s3/s3_client.h> |
14 | 14 |
|
| 15 | +#include <algorithm> |
| 16 | +#include <cstring> |
15 | 17 | #include <future> |
16 | 18 | #include <iomanip> |
17 | 19 | #include <list> |
@@ -284,6 +286,95 @@ void addHeader(aws_http_message *request, string_view name, string_view value) |
284 | 286 | aws_http_message_add_header(request, header); |
285 | 287 | } |
286 | 288 |
|
| 289 | +// A custom aws_input_stream that loops a small buffer to produce totalSize bytes. |
| 290 | +// This mirrors the Java runner's UploadFromRamStream: instead of allocating a buffer |
| 291 | +// equal to the full upload size, we reuse a small cache-friendly buffer repeatedly. |
| 292 | +struct LoopingUploadStream |
| 293 | +{ |
| 294 | + aws_allocator *alloc; |
| 295 | + const uint8_t *data; |
| 296 | + size_t dataLen; |
| 297 | + uint64_t totalSize; |
| 298 | + uint64_t bytesWritten; |
| 299 | +}; |
| 300 | + |
| 301 | +static int s_looping_stream_seek(aws_input_stream *stream, int64_t offset, enum aws_stream_seek_basis basis) |
| 302 | +{ |
| 303 | + auto *s = reinterpret_cast<LoopingUploadStream *>(stream->impl); |
| 304 | + if (basis == AWS_SSB_BEGIN) |
| 305 | + s->bytesWritten = (uint64_t)offset; |
| 306 | + else if (basis == AWS_SSB_END) |
| 307 | + s->bytesWritten = (uint64_t)((int64_t)s->totalSize + offset); |
| 308 | + return AWS_OP_SUCCESS; |
| 309 | +} |
| 310 | + |
| 311 | +static int s_looping_stream_read(aws_input_stream *stream, aws_byte_buf *dest) |
| 312 | +{ |
| 313 | + auto *s = reinterpret_cast<LoopingUploadStream *>(stream->impl); |
| 314 | + while (s->bytesWritten < s->totalSize && dest->len < dest->capacity) |
| 315 | + { |
| 316 | + uint64_t remaining = s->totalSize - s->bytesWritten; |
| 317 | + size_t space = dest->capacity - dest->len; |
| 318 | + size_t offset = (size_t)(s->bytesWritten % s->dataLen); |
| 319 | + size_t chunk = (size_t)std::min({remaining, (uint64_t)space, (uint64_t)(s->dataLen - offset)}); |
| 320 | + memcpy(dest->buffer + dest->len, s->data + offset, chunk); |
| 321 | + dest->len += chunk; |
| 322 | + s->bytesWritten += chunk; |
| 323 | + } |
| 324 | + return AWS_OP_SUCCESS; |
| 325 | +} |
| 326 | + |
| 327 | +static int s_looping_stream_get_status(aws_input_stream *stream, aws_stream_status *status) |
| 328 | +{ |
| 329 | + auto *s = reinterpret_cast<LoopingUploadStream *>(stream->impl); |
| 330 | + status->is_end_of_stream = (s->bytesWritten >= s->totalSize); |
| 331 | + status->is_valid = true; |
| 332 | + return AWS_OP_SUCCESS; |
| 333 | +} |
| 334 | + |
| 335 | +static int s_looping_stream_get_length(aws_input_stream *stream, int64_t *out_length) |
| 336 | +{ |
| 337 | + auto *s = reinterpret_cast<LoopingUploadStream *>(stream->impl); |
| 338 | + *out_length = (int64_t)s->totalSize; |
| 339 | + return AWS_OP_SUCCESS; |
| 340 | +} |
| 341 | + |
| 342 | +static aws_input_stream_vtable s_looping_stream_vtable = { |
| 343 | + .seek = s_looping_stream_seek, |
| 344 | + .read = s_looping_stream_read, |
| 345 | + .get_status = s_looping_stream_get_status, |
| 346 | + .get_length = s_looping_stream_get_length, |
| 347 | +}; |
| 348 | + |
| 349 | +static aws_input_stream *aws_input_stream_new_looping( |
| 350 | + aws_allocator *alloc, |
| 351 | + const uint8_t *data, |
| 352 | + size_t dataLen, |
| 353 | + uint64_t totalSize) |
| 354 | +{ |
| 355 | + auto *stream = reinterpret_cast<aws_input_stream *>(aws_mem_calloc(alloc, 1, sizeof(aws_input_stream))); |
| 356 | + auto *impl = reinterpret_cast<LoopingUploadStream *>(aws_mem_calloc(alloc, 1, sizeof(LoopingUploadStream))); |
| 357 | + impl->alloc = alloc; // store allocator so the destructor can use the same one |
| 358 | + impl->data = data; |
| 359 | + impl->dataLen = dataLen; |
| 360 | + impl->totalSize = totalSize; |
| 361 | + impl->bytesWritten = 0; |
| 362 | + stream->impl = impl; |
| 363 | + stream->vtable = &s_looping_stream_vtable; |
| 364 | + aws_ref_count_init( |
| 365 | + &stream->ref_count, |
| 366 | + stream, |
| 367 | + [](void *user_data) |
| 368 | + { |
| 369 | + auto *st = reinterpret_cast<aws_input_stream *>(user_data); |
| 370 | + auto *impl = reinterpret_cast<LoopingUploadStream *>(st->impl); |
| 371 | + aws_allocator *alloc = impl->alloc; // retrieve the allocator before freeing impl |
| 372 | + aws_mem_release(alloc, impl); |
| 373 | + aws_mem_release(alloc, st); |
| 374 | + }); |
| 375 | + return stream; |
| 376 | +} |
| 377 | + |
287 | 378 | Task::Task(CRunner &runner, size_t taskI, FILE *telemetryFile) |
288 | 379 | : runner(runner), taskI(taskI), config(runner.config.tasks[taskI]), donePromise(), |
289 | 380 | doneFuture(donePromise.get_future()) |
@@ -318,10 +409,11 @@ Task::Task(CRunner &runner, size_t taskI, FILE *telemetryFile) |
318 | 409 | options.send_filepath = toCursor(config.key); |
319 | 410 | else |
320 | 411 | { |
321 | | - // set up input-stream that uploads random data from a buffer |
322 | | - auto randomDataCursor = |
323 | | - aws_byte_cursor_from_array(runner.randomDataForUpload.data(), runner.randomDataForUpload.size()); |
324 | | - auto inMemoryStreamForUpload = aws_input_stream_new_from_cursor(runner.alloc, &randomDataCursor); |
| 412 | + // Set up a looping input-stream that repeatedly reads from a small buffer |
| 413 | + // to produce config.size bytes total. This is more cache-friendly than |
| 414 | + // allocating a buffer equal to the full upload size. |
| 415 | + inMemoryStreamForUpload = aws_input_stream_new_looping( |
| 416 | + runner.alloc, runner.randomDataForUpload.data(), runner.randomDataForUpload.size(), config.size); |
325 | 417 | aws_http_message_set_body_stream(request, inMemoryStreamForUpload); |
326 | 418 | aws_input_stream_release(inMemoryStreamForUpload); |
327 | 419 | } |
|
0 commit comments