File: s3_many_async_uploads_without_data_test.c

package info (click to toggle)
aws-crt-python 0.28.4%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 78,428 kB
  • sloc: ansic: 437,955; python: 27,657; makefile: 5,855; sh: 4,289; ruby: 208; java: 82; perl: 73; cpp: 25; xml: 11
file content (156 lines) | stat: -rw-r--r-- 6,489 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */
#include "s3_tester.h"

#include <aws/common/clock.h>
#include <aws/s3/private/s3_util.h>
#include <aws/testing/aws_test_harness.h>

/**
 * Regression test for deadlock discovered by a user of Mountpoint (which wraps aws-c-s3
 * with a filesystem-like API). The user opened MANY files at once.
 * The user wrote data to some of the later files they opened,
 * and waited for those writes to complete.
 * But aws-c-s3 was waiting on data from the first few files.
 * Both sides were waiting on each other. It was a deadlock.
 *
 * This test starts N upload meta-requests.
 * Then, it only sends data to 1 meta-request at a time, starting with the last
 * meta-request it created, and working backwards to the first.
 * If the test times out, then we still suffer from the deadlock.
 */

/* Number of simultaneous upload meta-requests to create */
#define MANY_ASYNC_UPLOADS_COUNT 200

/* Number of bytes each meta-request should upload (small so this this doesn't take forever) */
#define MANY_ASYNC_UPLOADS_OBJECT_SIZE 100

/* Bytes per write */
#define MANY_ASYNC_UPLOADS_BYTES_PER_WRITE 10

/* How long to spend doing nothing, before assuming we're deadlocked */
#define SEND_DATA_TIMEOUT_NANOS ((uint64_t)AWS_TIMESTAMP_NANOS * 10) /* 10secs */

/* See top of file for full description of what's going on in this test. */
AWS_TEST_CASE(test_s3_many_async_uploads_without_data, s_test_s3_many_async_uploads_without_data)
static int s_test_s3_many_async_uploads_without_data(struct aws_allocator *allocator, void *ctx) {
    (void)ctx;

    /* Set up */
    struct aws_s3_tester tester;
    ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));

    struct aws_s3_client *client = NULL;
    struct aws_s3_tester_client_options client_options;
    AWS_ZERO_STRUCT(client_options);
    ASSERT_SUCCESS(aws_s3_tester_client_new(&tester, &client_options, &client));

    struct aws_s3_meta_request *meta_requests[MANY_ASYNC_UPLOADS_COUNT];
    struct aws_s3_meta_request_test_results meta_request_test_results[MANY_ASYNC_UPLOADS_COUNT];

    /* Create N upload meta-requests, each with an async-input-stream that
     * won't provide data until later in this test... */
    for (int i = 0; i < MANY_ASYNC_UPLOADS_COUNT; ++i) {
        aws_s3_meta_request_test_results_init(&meta_request_test_results[i], allocator);

        struct aws_string *host_name =
            aws_s3_tester_build_endpoint_string(allocator, &g_test_bucket_name, &g_test_s3_region);
        struct aws_byte_cursor host_name_cursor = aws_byte_cursor_from_string(host_name);

        char object_name[128] = {0};
        snprintf(object_name, sizeof(object_name), "/many-async-uploads-%d.txt", i);
        struct aws_byte_buf object_path;
        ASSERT_SUCCESS(
            aws_s3_tester_upload_file_path_init(allocator, &object_path, aws_byte_cursor_from_c_str(object_name)));

        struct aws_http_message *message = aws_s3_test_put_object_request_new_without_body(
            allocator,
            &host_name_cursor,
            g_test_body_content_type,
            aws_byte_cursor_from_buf(&object_path),
            MANY_ASYNC_UPLOADS_OBJECT_SIZE,
            0 /*flags*/);

        /* Erase content-length header, because Mountpoint always uploads with unknown content-length */
        aws_http_headers_erase(aws_http_message_get_headers(message), g_content_length_header_name);

        struct aws_s3_meta_request_options options = {
            .type = AWS_S3_META_REQUEST_TYPE_PUT_OBJECT,
            .message = message,
            .send_using_async_writes = true,
        };
        ASSERT_SUCCESS(aws_s3_tester_bind_meta_request(&tester, &options, &meta_request_test_results[i]));

        meta_requests[i] = aws_s3_client_make_meta_request(client, &options);

        /* Release stuff created in this loop */
        aws_string_destroy(host_name);
        aws_byte_buf_clean_up(&object_path);
        aws_http_message_release(message);
    }

    /* Starting at the end, and working backwards, only provide data to one meta-request at a time. */
    for (int i = MANY_ASYNC_UPLOADS_COUNT - 1; i >= 0; --i) {

        struct aws_s3_meta_request *meta_request_i = meta_requests[i];

        /* Perform sequential writes to meta_request_i, until EOF */
        size_t bytes_written = 0;
        bool eof = false;
        while (!eof) {
            size_t bytes_to_write =
                aws_min_size(MANY_ASYNC_UPLOADS_BYTES_PER_WRITE, MANY_ASYNC_UPLOADS_OBJECT_SIZE - bytes_written);

            eof = (bytes_written + bytes_to_write) == MANY_ASYNC_UPLOADS_OBJECT_SIZE;

            /* use freshly allocated buffer for each write, so that we're likely to get memory violations
             * if this data is used wrong internally. */
            struct aws_byte_buf tmp_data;
            aws_byte_buf_init(&tmp_data, allocator, bytes_to_write);
            aws_byte_buf_write_u8_n(&tmp_data, 'z', bytes_to_write);

            struct aws_future_void *write_future =
                aws_s3_meta_request_write(meta_request_i, aws_byte_cursor_from_buf(&tmp_data), eof);

            ASSERT_TRUE(
                aws_future_void_wait(write_future, SEND_DATA_TIMEOUT_NANOS),
                "Timed out waiting to send data on upload %d/%d."
                " After writing %zu bytes, timed out on write(data=%zu, eof=%d)",
                i + 1,
                MANY_ASYNC_UPLOADS_COUNT,
                bytes_written,
                bytes_to_write,
                eof);

            /* write complete! */
            aws_byte_buf_clean_up(&tmp_data);

            ASSERT_INT_EQUALS(0, aws_future_void_get_error(write_future));
            aws_future_void_release(write_future);

            bytes_written += bytes_to_write;
        }
    }

    /* Wait for everything to finish */
    for (int i = 0; i < MANY_ASYNC_UPLOADS_COUNT; ++i) {
        meta_requests[i] = aws_s3_meta_request_release(meta_requests[i]);
    }

    aws_s3_tester_wait_for_meta_request_finish(&tester);
    aws_s3_tester_wait_for_meta_request_shutdown(&tester);

    for (int i = 0; i < MANY_ASYNC_UPLOADS_COUNT; ++i) {
        aws_s3_tester_validate_put_object_results(&meta_request_test_results[i], 0 /*flags*/);
        aws_s3_meta_request_test_results_clean_up(&meta_request_test_results[i]);
    }

    /* Cleanup */
    aws_s3_client_release(client);
    aws_s3_tester_clean_up(&tester);

    return 0;
}