aboutsummaryrefslogtreecommitdiff
path: root/tests/unit/test-nested-aio-poll.c
blob: 9bbe18b839bdd37f4c5fb574e09519fdbd7a46d4 (plain)
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
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Test that poll handlers are not re-entrant in nested aio_poll()
 *
 * Copyright Red Hat
 *
 * Poll handlers are usually level-triggered. That means they continue firing
 * until the condition is reset (e.g. a virtqueue becomes empty). If a poll
 * handler calls nested aio_poll() before the condition is reset, then infinite
 * recursion occurs.
 *
 * aio_poll() is supposed to prevent this by disabling poll handlers in nested
 * aio_poll() calls. This test case checks that this is indeed what happens.
 */
#include "qemu/osdep.h"
#include "block/aio.h"
#include "qapi/error.h"

typedef struct {
    AioContext *ctx;

    /* This is the EventNotifier that drives the test */
    EventNotifier poll_notifier;

    /* This EventNotifier is only used to wake aio_poll() */
    EventNotifier dummy_notifier;

    bool nested;
} TestData;

static void io_read(EventNotifier *notifier)
{
    fprintf(stderr, "%s %p\n", __func__, notifier);
    event_notifier_test_and_clear(notifier);
}

static bool io_poll_true(void *opaque)
{
    fprintf(stderr, "%s %p\n", __func__, opaque);
    return true;
}

static bool io_poll_false(void *opaque)
{
    fprintf(stderr, "%s %p\n", __func__, opaque);
    return false;
}

static void io_poll_ready(EventNotifier *notifier)
{
    TestData *td = container_of(notifier, TestData, poll_notifier);

    fprintf(stderr, "> %s\n", __func__);

    g_assert(!td->nested);
    td->nested = true;

    /* Wake the following nested aio_poll() call */
    event_notifier_set(&td->dummy_notifier);

    /* This nested event loop must not call io_poll()/io_poll_ready() */
    g_assert(aio_poll(td->ctx, true));

    td->nested = false;

    fprintf(stderr, "< %s\n", __func__);
}

/* dummy_notifier never triggers */
static void io_poll_never_ready(EventNotifier *notifier)
{
    g_assert_not_reached();
}

static void test(void)
{
    TestData td = {
        .ctx = aio_context_new(&error_abort),
    };

    qemu_set_current_aio_context(td.ctx);

    /* Enable polling */
    aio_context_set_poll_params(td.ctx, 1000000, 2, 2, &error_abort);

    /*
     * The GSource is unused but this has the side-effect of changing the fdmon
     * that AioContext uses.
     */
    aio_get_g_source(td.ctx);

    /* Make the event notifier active (set) right away */
    event_notifier_init(&td.poll_notifier, 1);
    aio_set_event_notifier(td.ctx, &td.poll_notifier, false,
                           io_read, io_poll_true, io_poll_ready);

    /* This event notifier will be used later */
    event_notifier_init(&td.dummy_notifier, 0);
    aio_set_event_notifier(td.ctx, &td.dummy_notifier, false,
                           io_read, io_poll_false, io_poll_never_ready);

    /* Consume aio_notify() */
    g_assert(!aio_poll(td.ctx, false));

    /*
     * Run the io_read() handler. This has the side-effect of activating
     * polling in future aio_poll() calls.
     */
    g_assert(aio_poll(td.ctx, true));

    /* The second time around the io_poll()/io_poll_ready() handler runs */
    g_assert(aio_poll(td.ctx, true));

    /* Run io_poll()/io_poll_ready() one more time to show it keeps working */
    g_assert(aio_poll(td.ctx, true));

    aio_set_event_notifier(td.ctx, &td.dummy_notifier, false,
                           NULL, NULL, NULL);
    aio_set_event_notifier(td.ctx, &td.poll_notifier, false, NULL, NULL, NULL);
    event_notifier_cleanup(&td.dummy_notifier);
    event_notifier_cleanup(&td.poll_notifier);
    aio_context_unref(td.ctx);
}

int main(int argc, char **argv)
{
    g_test_init(&argc, &argv, NULL);
    g_test_add_func("/nested-aio-poll", test);
    return g_test_run();
}