@@ -43,4 +43,100 @@ TEST_CASE("consume async_stream")
43
43
CHECK (result == 3 );
44
44
}
45
45
46
+ TEST_CASE (" producer exiting early on destruction of stream" )
47
+ {
48
+ int lastProduced = -1 ;
49
+ bool ranFinalisation = false ;
50
+
51
+ auto subscribable = make_subscribable (
52
+ [&]() -> async_stream_subscription<int >
53
+ {
54
+ for (int i = 0 ; i < 5 ; ++i)
55
+ {
56
+ lastProduced = i;
57
+ bool produceMore = co_yield i;
58
+ if (!produceMore) break ;
59
+ }
60
+
61
+ ranFinalisation = true ;
62
+ });
63
+
64
+ sync_wait (consume (subscribable, [](async_stream<int > stream) -> task<>
65
+ {
66
+ // TRICKY: Need to move parameter to local var here to ensure it is destructed when the
67
+ // coroutine runs to completion. Normally, the destructor of parameters won't run until
68
+ // the coroutine is destroyed. However, the coroutine won't be destroyed until the
69
+ // entire consume operation completes, which includes waiting until the producer coroutine
70
+ // runs to completion. However, the producer coroutine isn't going to run to completion
71
+ // unless we either consume it completely or detach from the stream, causing a deadlock.
72
+ auto localStream = std::move (stream);
73
+ for co_await (int value : localStream)
74
+ {
75
+ // Don't consume any more values once we get to '3'.
76
+ if (value == 3 ) break ;
77
+ }
78
+ }));
79
+
80
+ CHECK (lastProduced == 3 );
81
+ CHECK (ranFinalisation);
82
+ }
83
+
84
+ template <typename COUNT, typename SUBSCRIBABLE>
85
+ auto take (COUNT n, SUBSCRIBABLE&& s)
86
+ {
87
+ return make_subscribable (
88
+ [n, s = std::forward<SUBSCRIBABLE>(s)]
89
+ {
90
+ auto [sourceStream, sourceTask] = s.subscribe ();
91
+
92
+ using value_type = typename std::remove_reference_t <decltype (sourceStream)>::value_type;
93
+
94
+ auto [outputStream, outputTask] = [](COUNT n, auto stream) -> async_stream_subscription<value_type>
95
+ {
96
+ auto localStream = std::move (stream);
97
+ if (n > 0 )
98
+ {
99
+ for co_await (auto && value : localStream)
100
+ {
101
+ bool produceMore = co_yield value;
102
+ if (!produceMore) break ;
103
+ if (--n == 0 ) break ;
104
+ }
105
+ }
106
+ }(n, std::move (sourceStream));
107
+
108
+ return std::make_tuple (
109
+ std::move (outputStream),
110
+ when_all (std::move (sourceTask), std::move (outputTask)) | fmap ([](auto ) {}));
111
+ });
112
+ }
113
+
114
+ TEST_CASE (" take(5)" )
115
+ {
116
+ auto subscribable = take (5 , make_subscribable ([]() -> async_stream_subscription<int >
117
+ {
118
+ for (int i = 0 ; i < 10 ; ++i)
119
+ {
120
+ bool produceMore = co_yield i;
121
+ if (!produceMore) break ;
122
+ }
123
+ }));
124
+
125
+ auto values = sync_wait (consume (subscribable, [](async_stream<int > stream) -> task<std::vector<int >>
126
+ {
127
+ auto localStream = std::move (stream);
128
+
129
+ std::vector<int > values;
130
+ for co_await (int value : localStream)
131
+ {
132
+ values.push_back (value);
133
+ }
134
+ return std::move (values);
135
+ }));
136
+
137
+ CHECK (values.size () == 5 );
138
+ CHECK (values[0 ] == 0 );
139
+ CHECK (values[4 ] == 4 );
140
+ }
141
+
46
142
TEST_SUITE_END ();
0 commit comments