-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] view::async_buffer #1205
Conversation
failed macOS tests are due to #1206 |
Codecov Report
@@ Coverage Diff @@
## master #1205 +/- ##
==========================================
+ Coverage 96.83% 96.84% +<.01%
==========================================
Files 218 219 +1
Lines 8748 8801 +53
==========================================
+ Hits 8471 8523 +52
- Misses 277 278 +1
Continue to review full report at Codecov.
|
class async_buffer_iterator | ||
{ | ||
//!\brief The sentinel type to compare to. | ||
using sentinel_type = std::ranges::default_sentinel_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still open
|
||
while ((it != e) && (!stop_producer.load()))// TODO likely | ||
{ | ||
auto tmp = std::move(*it); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entire function can be reduced to:
void produce()
{
for (auto it = begin(urange); it != end(urange); ++it)
if (buffer.wait_push(std::iter_move(it)) == queue_op_status::closed)
break;
buffer.close();
}
Then you can directly initialise it as a lambda function in the thread intialisation. But this is not important to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original implementation was very close to this. I changed it after I got dead-locks, but looking at the code right now, I am not sure why exactly (I was under the false impression that try_push would report queue_op_status::full
instead queue_op_status::closed
if the queue is both full and closed).
I will investigate this again.
using test_type = ::testing::Types<iterator_type>; | ||
INSTANTIATE_TYPED_TEST_CASE_P(iterator_fixture, iterator_fixture, test_type); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double new line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still open
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
most of the minor comments where annotated but actually not fixed. Please check this again.
And we need to discuss another design if you really intend to have consumers not eating up the entire queue. I have layout one idea, but let's discuss this tomorrow with a fresh mind.
* Typically this adaptor is used when you want to consume the entire underlying range; there is no way to stop | ||
* this view from buffering elements before the end of the underlying range is reached other than destructing it. | ||
* If it is destructed before the end of the underlying range is reached, it will stop consuming elements, however, | ||
* those elements that are currently in it's buffer are destructed with it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😱 No this is a very bad design. If you want to allow that consumers are not consuming everything you need a different signaling mechanism, i.e. consumers signal that they are done reading. The simplest way to do it is by using a barrier then. In that case you need to specify in the construction of the view how many consumers you are expecting and the destructor needs to wait for the consumers to signal they are done (arrived at the barrier managed by the view). Then you can safely destruct the queue even if it is not empy after closing since you are sure that no one is reading from it anymore. I need to think about how you can make it work nicely with the iterator.
5f031e5
to
94b3e60
Compare
I have adapted everything according to our discussion.
Please review your comments and mark resolved issues as such so that I know which ones you are referring to. (Some things have been changed outside the immediate context so it may appear as there was no change when there was). |
class async_buffer_iterator | ||
{ | ||
//!\brief The sentinel type to compare to. | ||
using sentinel_type = std::ranges::default_sentinel_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still open
|
||
detail::spin_delay delay{}; | ||
|
||
assert (buffer_ptr != nullptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still open
* the underlying range will be invalidated! For underlying ranges that are single-pass, this makes no difference, but | ||
* it might be unexpected for multi-pass ranges (std::ranges::ForwardRange). | ||
* | ||
* Typically this adaptor is used when you want to consume the entire underlying range. While destructing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Typically this adaptor is used when you want to consume the entire underlying range. While destructing | |
* Typically this adaptor is used when you want to consume the entire underlying range. Destructing |
* | ||
* Typically this adaptor is used when you want to consume the entire underlying range. While destructing | ||
* this view before all elements have been read will also stop the thread that moves object from the underlying | ||
* range, it is difficult to assess which elements in the underlying range might still be valid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* range, it is difficult to assess which elements in the underlying range might still be valid. | |
* range. |
* Typically this adaptor is used when you want to consume the entire underlying range. While destructing | ||
* this view before all elements have been read will also stop the thread that moves object from the underlying | ||
* range, it is difficult to assess which elements in the underlying range might still be valid. | ||
* **In general, assume that it is not safe to access the underlying range separately once it has been passed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* **In general, assume that it is not safe to access the underlying range separately once it has been passed | |
* **In general, safe access to the underlying range in any other context is not guaranteed and once it has been passed |
* | ||
* Note that in addition to the buffer of the view, every iterator has its own one-element-buffer. Dereferencing | ||
* the iterator returns a reference to the element in the buffer, usually you will want to move this element out | ||
* of the buffer with std::move. Incrementing the iterator refills the buffer from the queue inside the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* of the buffer with std::move. Incrementing the iterator refills the buffer from the queue inside the | |
* of the buffer with std::move or std::iter_move. Incrementing the iterator refills the buffer from the queue inside the |
using test_type = ::testing::Types<iterator_type>; | ||
INSTANTIATE_TYPED_TEST_CASE_P(iterator_fixture, iterator_fixture, test_type); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still open
Combinability is broken, don't merge this yet. |
I have addressed the last issues, I think. I even moved the iterator definition out 🤸♂️ |
06e93f2
to
414a66f
Compare
Fixed the test failure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some minor questions left
//!\brief Post-increment. | ||
void operator++(int) noexcept | ||
{ | ||
++(*this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so this iterator becomes no Cpp17Iterator. In case this matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not required: https://en.cppreference.com/w/cpp/named_req/InputIterator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
value_type x = *i;
++i;
return x;
*i++ -> convertible to value_type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And further down:
typename std::common_reference_t<decltype(*i++)&&, typename std::readable_traits::value_type&>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct, but we wouldn't be able to satisfy "equivalent expression" in any case, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we leave it as no CPP17Iterator. You might want to add then a sentence to the view description that the returned iterator does not model Cpp17InputIterator. After this we can merge it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comment about not modeling Cpp17InputIterator to the view documentation.
414a66f
to
07b9122
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💅 only minor stuff
* And we want it to happen to make sure we don't dead-lock on it. | ||
*/ | ||
std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
empty line
07b9122
to
e78930d
Compare
CI is broken |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one more test, than I am happy 😏
EXPECT_FALSE(std::ranges::SizedRange<decltype(v1)>); | ||
EXPECT_FALSE(ConstIterableRange<decltype(v1)>); | ||
EXPECT_TRUE(std::ranges::View<decltype(v1)>); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe add one more test with an empty range.
Jenkins failure unrelated. |
Enables async I/O even with multiple consumer threads.
Does not yet model View, because it is not Semiregular. I don't mind merging this now and adapting the tests when range-v3 is brought up to date.
TODO: