You are viewing a plain text version of this content. The canonical link for it is here.
Posted to github@arrow.apache.org by GitBox <gi...@apache.org> on 2021/03/23 13:23:56 UTC

[GitHub] [arrow] westonpace opened a new pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

westonpace opened a new pull request #9779:
URL: https://github.com/apache/arrow/pull/9779


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601829862



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }

Review comment:
       Good observation.  I reworked the logic based on this and I think it is much clearer now (and I got rid of the optional)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#issuecomment-807921383


   Failing check unrelated.  I believe I have addressed all feedback.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] lidavidm commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
lidavidm commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r599902529



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b
+///
+/// IsNext should be a BinaryPredicate that returns true, given `a` and `b`, only if
+/// `b` follows immediately after `a`.  It should return true given `initial_value` and
+/// `b` if `b` is the first item in the sequence.
+///
+/// This operator will queue unboundedly while waiting for the next item.  It is intended
+/// for jittery sources that might scatter an ordered sequence.  It is NOT intended to
+/// sort.  Using it to try and sort could result in deadlock.  Imagine a sequence of 10
+/// items returned in reverse order with a readahead of 8.  The first item will never
+/// reach this operator and the chain will halt.

Review comment:
       Yeah, that sounds right to me.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] pitrou commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
pitrou commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601832723



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;

Review comment:
       Let's keep as-is then.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r599852706



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {

Review comment:
       I capture finished while I'm still in the critical section.  I can't call `AddCallback` or `MarkFinished` while holding the mutex.  So the first block is figuring out which action to take (queue, do nothing, mark existing finished) and the second block is performing the action.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] pitrou commented on pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
pitrou commented on pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#issuecomment-810076654


   CI failures are unrelated, will merge.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601844175



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {

Review comment:
       I added one.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] lidavidm commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
lidavidm commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r599611113



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b
+///
+/// IsNext should be a BinaryPredicate that returns true, given `a` and `b`, only if
+/// `b` follows immediately after `a`.  It should return true given `initial_value` and
+/// `b` if `b` is the first item in the sequence.
+///
+/// This operator will queue unboundedly while waiting for the next item.  It is intended
+/// for jittery sources that might scatter an ordered sequence.  It is NOT intended to
+/// sort.  Using it to try and sort could result in deadlock.  Imagine a sequence of 10
+/// items returned in reverse order with a readahead of 8.  The first item will never
+/// reach this operator and the chain will halt.

Review comment:
       I worry this is a footgun, or will lead to hard-to-debug deadlocks, because even if we don't use this to sort per se, this paragraph means that any reordering beyond the readahead 'window' will lead to a deadlock, right? E.g. if you get items in the order 2 3 4 5 1 and have readahead = 4. Unless we have a way to limit how much reordering happens. (I'm assuming this is intended for use with MergeMap, but even if you're only merging two sources, you could have unbounded reordering if the first source is slow.)

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b
+///
+/// IsNext should be a BinaryPredicate that returns true, given `a` and `b`, only if
+/// `b` follows immediately after `a`.  It should return true given `initial_value` and
+/// `b` if `b` is the first item in the sequence.
+///
+/// This operator will queue unboundedly while waiting for the next item.  It is intended
+/// for jittery sources that might scatter an ordered sequence.  It is NOT intended to
+/// sort.  Using it to try and sort could result in deadlock.  Imagine a sequence of 10
+/// items returned in reverse order with a readahead of 8.  The first item will never
+/// reach this operator and the chain will halt.

Review comment:
       Though, from my reading of the implementation, I think this _will_ effectively sort? Since the generator will (non-async-reentrantly) pull from the underlying generator until it reaches the 'next' item.

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {

Review comment:
       nit: collapse these branches?

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {

Review comment:
       nit: it seems redundant to have a local finished variable given that it always has the value of state->finished




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] pitrou commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
pitrou commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r603274804



##########
File path: cpp/src/arrow/util/async_generator_test.cc
##########
@@ -793,6 +794,118 @@ TEST(TestAsyncUtil, ReadaheadFailed) {
   ASSERT_TRUE(IsIterationEnd(definitely_last));
 }
 
+class SequencerTestFixture : public GeneratorTestFixture {
+ protected:
+  void RandomShuffle(std::vector<TestInt>& values) {
+    std::default_random_engine gen(seed_++);
+    std::shuffle(values.begin(), values.end(), gen);
+  }
+
+  int seed_ = 42;
+  std::function<bool(const TestInt&, const TestInt&)> cmp_ =
+      [](const TestInt& left, const TestInt& right) { return left.value > right.value; };
+  // Let's increment by 2's to make it interesting
+  std::function<bool(const TestInt&, const TestInt&)> is_next_ =
+      [](const TestInt& left, const TestInt& right) {
+        return left.value + 2 == right.value;
+      };
+};
+
+TEST_P(SequencerTestFixture, SequenceBasic) {
+  // Basic sequencing
+  auto original = MakeSource({6, 4, 2});
+  auto sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(0));
+  AssertAsyncGeneratorMatch({2, 4, 6}, sequenced);
+
+  // From ordered input
+  original = MakeSource({2, 4, 6});
+  sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(0));
+  AssertAsyncGeneratorMatch({2, 4, 6}, sequenced);
+}
+
+TEST_P(SequencerTestFixture, SequenceLambda) {
+  auto cmp = [](const TestInt& left, const TestInt& right) {
+    return left.value > right.value;
+  };
+  auto is_next = [](const TestInt& left, const TestInt& right) {
+    return left.value + 2 == right.value;
+  };
+  // Basic sequencing
+  auto original = MakeSource({6, 4, 2});
+  auto sequenced = MakeSequencingGenerator(original, cmp, is_next, TestInt(0));
+  AssertAsyncGeneratorMatch({2, 4, 6}, sequenced);
+}
+
+TEST_P(SequencerTestFixture, SequenceError) {
+  {
+    auto original = MakeSource({6, 4, 2});
+    original = FailsAt(original, 1);
+    auto sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(0));
+    auto collected = CollectAsyncGenerator(sequenced);
+    ASSERT_FINISHES_AND_RAISES(Invalid, collected);
+  }
+  {
+    // Failure should clear old items out of the queue immediately
+    // shared_ptr versions of cmp_ and is_next_
+    auto cmp = cmp_;
+    std::function<bool(const std::shared_ptr<TestInt>&, const std::shared_ptr<TestInt>&)>
+        ptr_cmp =
+            [cmp](const std::shared_ptr<TestInt>& left,
+                  const std::shared_ptr<TestInt>& right) { return cmp(*left, *right); };
+    auto is_next = is_next_;
+    std::function<bool(const std::shared_ptr<TestInt>&, const std::shared_ptr<TestInt>&)>
+        ptr_is_next = [is_next](const std::shared_ptr<TestInt>& left,
+                                const std::shared_ptr<TestInt>& right) {
+          return is_next(*left, *right);
+        };
+
+    PushGenerator<std::shared_ptr<TestInt>> source;
+    auto sequenced = MakeSequencingGenerator(
+        static_cast<AsyncGenerator<std::shared_ptr<TestInt>>>(source), ptr_cmp,
+        ptr_is_next, std::make_shared<TestInt>(0));
+
+    auto should_be_cleared = std::make_shared<TestInt>(4);
+    std::weak_ptr<TestInt> ref = should_be_cleared;
+    auto producer = source.producer();
+    auto next_fut = sequenced();
+    producer.Push(std::move(should_be_cleared));
+    producer.Push(Status::Invalid("XYZ"));
+    ASSERT_TRUE(ref.expired());
+
+    ASSERT_FINISHES_AND_RAISES(Invalid, next_fut);
+  }
+  {
+    // Failure should interrupt pumping
+    PushGenerator<TestInt> source;
+    auto sequenced = MakeSequencingGenerator(static_cast<AsyncGenerator<TestInt>>(source),
+                                             cmp_, is_next_, TestInt(0));
+
+    auto producer = source.producer();
+    auto next_fut = sequenced();
+    producer.Push(TestInt(4));
+    producer.Push(Status::Invalid("XYZ"));
+    producer.Push(TestInt(2));
+    ASSERT_FINISHES_AND_RAISES(Invalid, next_fut);
+    // The sequencer should not have pulled the 2 out of the source because it should
+    // have stopped pumping on error
+    ASSERT_FINISHES_OK_AND_EQ(TestInt(2), source());
+  }
+}
+
+TEST_P(SequencerTestFixture, SequenceStress) {
+  constexpr int NITEMS = 100;
+  for (auto task_index = 0; task_index < GetNumItersForStress(); task_index++) {
+    auto input = RangeVector(NITEMS, 2);
+    RandomShuffle(input);
+    auto original = MakeSource(input);
+    auto sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(-2));
+    AssertAsyncGeneratorMatch(RangeVector(NITEMS, 2), sequenced);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(SequencerTests, SequencerTestFixture,
+                         ::testing::Values(false, true));

Review comment:
       I'm probably being dense or distracted, but I don't see the parameter being used anywhere. Could you point it out to me? :-S




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r602644112



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();

Review comment:
       Ah, `= {}` fails because there is no assignment and `swap` fails because I cannot create a queue without an instance of `comp`.  I can create a second empty queue when I create the first one but that is kind of clumsy and it can only swap once (although it should only have to swap once).
   
   At that point, it would probably just be cleaner to have a dedicated spot for the "final result" and call the destructor on the queue instead of clearing it.
   
   I think I'll just keep it how it is.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601829413



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b

Review comment:
       Yes, we want a specific kind of Compare anyways.  The STL language is very confusing "if the first argument of the call appears before the second in the strict weak ordering relation induced by this type".  The "strict weak ordering relation" in this case is largest-to-smallest so the bigger item comes before the smaller in the relation. I went ahead and just made it plain English.
   
   A priority queue with std::less (smallest-to-largest) puts the largest item on top and std::greater (largest-to-smallest) puts the smallest on top.  Since we want the smallest it is more of a "greater than" relationship.
   
   I renamed it to ComesAfter.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#issuecomment-806482907


   Only failing build was rust.  Rebased to pull in fix.  Consider merging if CI passes.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601830091



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;

Review comment:
       Done.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r599849880



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b
+///
+/// IsNext should be a BinaryPredicate that returns true, given `a` and `b`, only if
+/// `b` follows immediately after `a`.  It should return true given `initial_value` and
+/// `b` if `b` is the first item in the sequence.
+///
+/// This operator will queue unboundedly while waiting for the next item.  It is intended
+/// for jittery sources that might scatter an ordered sequence.  It is NOT intended to
+/// sort.  Using it to try and sort could result in deadlock.  Imagine a sequence of 10
+/// items returned in reverse order with a readahead of 8.  The first item will never
+/// reach this operator and the chain will halt.

Review comment:
       Ok, you're right.  I had been worrying about the footgun too but you are correct that it won't actually matter what the upstream readahead is set to.  Instead, if you do something foolish (e.g. try and sort input with this), then the failure behavior will be excessive RAM usage and not deadlock.  This is probably preferred.  The max this can queue will depend on how out-of-order your sources can get.  If that becomes a problem then we can modify AddReadahead to have max_readahead and max_ahead_of_first.  I'll update the comment but does that logic seem right?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r600007226



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b
+///
+/// IsNext should be a BinaryPredicate that returns true, given `a` and `b`, only if
+/// `b` follows immediately after `a`.  It should return true given `initial_value` and
+/// `b` if `b` is the first item in the sequence.
+///
+/// This operator will queue unboundedly while waiting for the next item.  It is intended
+/// for jittery sources that might scatter an ordered sequence.  It is NOT intended to
+/// sort.  Using it to try and sort could result in deadlock.  Imagine a sequence of 10
+/// items returned in reverse order with a readahead of 8.  The first item will never
+/// reach this operator and the chain will halt.

Review comment:
       Ok, I have updated the comments.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] github-actions[bot] commented on pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
github-actions[bot] commented on pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#issuecomment-804986295


   https://issues.apache.org/jira/browse/ARROW-12056


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] pitrou commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
pitrou commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601240917



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b

Review comment:
       Hmm, the STL doc says "true if the first argument of the call appears _before_ the second".

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }

Review comment:
       Moving here `return Future<T>::MakeFinished(*result)` here will make this function slightly more readable.

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();

Review comment:
       Wow, `std::priority_queue` doesn't allow to clear the queue in a single call? (except by using `std::swap` it seems...)

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -278,6 +278,147 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {

Review comment:
       You may want to add a comment pointing this out, so that this doesn't look gratuitously weird.

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;

Review comment:
       I'm not sure this is correct. Do you mean something like (or the converse):
   ```c++
       bool operator()(const Result<T>& left, const Result<T>& right) {
         // Errors always come before non-errors
         if (!left.ok()) {
           return false;
         }
         if (right.ok()) {
           return true;
         }
         return compare(*left, *right);
       }
   ```

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();
+          }
+          ready_to_deliver = true;
+          state_->finished = true;
+        } else if (IsIterationEnd<T>(result.ValueUnsafe())) {
+          ready_to_deliver = state_->queue.empty();
+          state_->finished = true;
+        } else {
+          ready_to_deliver = state_->is_next(state_->previous_value, *result);
+        }
+
+        if (ready_to_deliver && state_->waiting_future.is_valid()) {
+          to_deliver = state_->waiting_future;
+          if (result.ok()) {
+            state_->previous_value = *result;
+          }
+        } else {
+          state_->queue.push(result);
+        }
+        finished = state_->finished;
+      }
+      if (to_deliver.is_valid()) {
+        to_deliver.MarkFinished(result);
+      } else {
+        if (!finished) {
+          state_->source().AddCallback(Callback{state_});
+        }
+      }
+    }
+
+   private:
+    const std::shared_ptr<State> state_;
+  };
+
+  const std::shared_ptr<State> state_;
+};
+
+/// \brief Buffers an AsyncGenerator to return values in sequence order  Comp and IsNext
+/// determine the sequence order.
+///
+/// Comp should be STL Compare https://en.cppreference.com/w/cpp/named_req/Compare It
+/// should return true only if a is later than b

Review comment:
       Can Comp be renamed something more explicit, such as "IsBefore" or "IsAfter" (or "ComesBefore" etc.)?

##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;

Review comment:
       I don't think there's a reason to declare this upfront using an `optional` if you reorganize the `if` branches a bit.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601831609



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;

Review comment:
       You're right in a way.  However, once an error is placed in the queue there should never again be another item placed on the queue so this operator should not be called on an invalid result.
   Would it be better to just use ValueUnsafe directly and let it explode?  I suppose that's the equivalent of DCHECK_OK (which I think I can't use in this context).




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r603407695



##########
File path: cpp/src/arrow/util/async_generator_test.cc
##########
@@ -793,6 +794,118 @@ TEST(TestAsyncUtil, ReadaheadFailed) {
   ASSERT_TRUE(IsIterationEnd(definitely_last));
 }
 
+class SequencerTestFixture : public GeneratorTestFixture {
+ protected:
+  void RandomShuffle(std::vector<TestInt>& values) {
+    std::default_random_engine gen(seed_++);
+    std::shuffle(values.begin(), values.end(), gen);
+  }
+
+  int seed_ = 42;
+  std::function<bool(const TestInt&, const TestInt&)> cmp_ =
+      [](const TestInt& left, const TestInt& right) { return left.value > right.value; };
+  // Let's increment by 2's to make it interesting
+  std::function<bool(const TestInt&, const TestInt&)> is_next_ =
+      [](const TestInt& left, const TestInt& right) {
+        return left.value + 2 == right.value;
+      };
+};
+
+TEST_P(SequencerTestFixture, SequenceBasic) {
+  // Basic sequencing
+  auto original = MakeSource({6, 4, 2});
+  auto sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(0));
+  AssertAsyncGeneratorMatch({2, 4, 6}, sequenced);
+
+  // From ordered input
+  original = MakeSource({2, 4, 6});
+  sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(0));
+  AssertAsyncGeneratorMatch({2, 4, 6}, sequenced);
+}
+
+TEST_P(SequencerTestFixture, SequenceLambda) {
+  auto cmp = [](const TestInt& left, const TestInt& right) {
+    return left.value > right.value;
+  };
+  auto is_next = [](const TestInt& left, const TestInt& right) {
+    return left.value + 2 == right.value;
+  };
+  // Basic sequencing
+  auto original = MakeSource({6, 4, 2});
+  auto sequenced = MakeSequencingGenerator(original, cmp, is_next, TestInt(0));
+  AssertAsyncGeneratorMatch({2, 4, 6}, sequenced);
+}
+
+TEST_P(SequencerTestFixture, SequenceError) {
+  {
+    auto original = MakeSource({6, 4, 2});
+    original = FailsAt(original, 1);
+    auto sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(0));
+    auto collected = CollectAsyncGenerator(sequenced);
+    ASSERT_FINISHES_AND_RAISES(Invalid, collected);
+  }
+  {
+    // Failure should clear old items out of the queue immediately
+    // shared_ptr versions of cmp_ and is_next_
+    auto cmp = cmp_;
+    std::function<bool(const std::shared_ptr<TestInt>&, const std::shared_ptr<TestInt>&)>
+        ptr_cmp =
+            [cmp](const std::shared_ptr<TestInt>& left,
+                  const std::shared_ptr<TestInt>& right) { return cmp(*left, *right); };
+    auto is_next = is_next_;
+    std::function<bool(const std::shared_ptr<TestInt>&, const std::shared_ptr<TestInt>&)>
+        ptr_is_next = [is_next](const std::shared_ptr<TestInt>& left,
+                                const std::shared_ptr<TestInt>& right) {
+          return is_next(*left, *right);
+        };
+
+    PushGenerator<std::shared_ptr<TestInt>> source;
+    auto sequenced = MakeSequencingGenerator(
+        static_cast<AsyncGenerator<std::shared_ptr<TestInt>>>(source), ptr_cmp,
+        ptr_is_next, std::make_shared<TestInt>(0));
+
+    auto should_be_cleared = std::make_shared<TestInt>(4);
+    std::weak_ptr<TestInt> ref = should_be_cleared;
+    auto producer = source.producer();
+    auto next_fut = sequenced();
+    producer.Push(std::move(should_be_cleared));
+    producer.Push(Status::Invalid("XYZ"));
+    ASSERT_TRUE(ref.expired());
+
+    ASSERT_FINISHES_AND_RAISES(Invalid, next_fut);
+  }
+  {
+    // Failure should interrupt pumping
+    PushGenerator<TestInt> source;
+    auto sequenced = MakeSequencingGenerator(static_cast<AsyncGenerator<TestInt>>(source),
+                                             cmp_, is_next_, TestInt(0));
+
+    auto producer = source.producer();
+    auto next_fut = sequenced();
+    producer.Push(TestInt(4));
+    producer.Push(Status::Invalid("XYZ"));
+    producer.Push(TestInt(2));
+    ASSERT_FINISHES_AND_RAISES(Invalid, next_fut);
+    // The sequencer should not have pulled the 2 out of the source because it should
+    // have stopped pumping on error
+    ASSERT_FINISHES_OK_AND_EQ(TestInt(2), source());
+  }
+}
+
+TEST_P(SequencerTestFixture, SequenceStress) {
+  constexpr int NITEMS = 100;
+  for (auto task_index = 0; task_index < GetNumItersForStress(); task_index++) {
+    auto input = RangeVector(NITEMS, 2);
+    RandomShuffle(input);
+    auto original = MakeSource(input);
+    auto sequenced = MakeSequencingGenerator(original, cmp_, is_next_, TestInt(-2));
+    AssertAsyncGeneratorMatch(RangeVector(NITEMS, 2), sequenced);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(SequencerTests, SequencerTestFixture,
+                         ::testing::Values(false, true));

Review comment:
       I do my best to obscure it :).  SequencerTestFixture extends GeneratorTestFixture which has a `MakeSource` method.  The parameter there is named `slow`.  `MakeSource` either returns a fast source (this is the false case: synchronous source, no delays) or a source that runs `SleepABit` between each item.  This helps test the case where the calls to `AddCallback`/`Then` return immediately (when this parameter is false) or not (when this parameter is true).  Sometimes in various generators I will get errors in one and not the other.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] pitrou closed pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
pitrou closed pull request #9779:
URL: https://github.com/apache/arrow/pull/9779


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] pitrou commented on pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
pitrou commented on pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#issuecomment-809495463


   Note conflicts need fixing now.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r601832029



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();

Review comment:
       Some googling suggests creating a new queue should work well so I went with that.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [arrow] westonpace commented on a change in pull request #9779: ARROW-12056: [C++] Create sequencing AsyncGenerator

Posted by GitBox <gi...@apache.org>.
westonpace commented on a change in pull request #9779:
URL: https://github.com/apache/arrow/pull/9779#discussion_r602642902



##########
File path: cpp/src/arrow/util/async_generator.h
##########
@@ -280,6 +280,150 @@ AsyncGenerator<V> MakeMappedGenerator(AsyncGenerator<T> source_generator,
   return MappingGenerator<T, V>(std::move(source_generator), std::move(map));
 }
 
+/// \see MakeSequencingGenerator
+template <typename T, typename Comp, typename IsNext>
+class SequencingGenerator {
+ public:
+  SequencingGenerator(AsyncGenerator<T> source, Comp compare, IsNext is_next,
+                      T initial_value)
+      : state_(std::make_shared<State>(std::move(source), std::move(compare),
+                                       std::move(is_next), std::move(initial_value))) {}
+
+  Future<T> operator()() {
+    util::optional<Result<T>> result;
+    {
+      auto guard = state_->mutex.Lock();
+      if (!state_->queue.empty()) {
+        if (!state_->queue.top().ok() ||
+            state_->is_next(state_->previous_value, *state_->queue.top())) {
+          result = std::move(state_->queue.top());
+          if (result->ok()) {
+            state_->previous_value = **result;
+          }
+          state_->queue.pop();
+        }
+      }
+      if (!result.has_value()) {
+        if (state_->finished) {
+          return AsyncGeneratorEnd<T>();
+        }
+        auto new_waiting_fut = Future<T>::Make();
+        state_->waiting_future = new_waiting_fut;
+        guard.Unlock();
+        state_->source().AddCallback(Callback{state_});
+        return new_waiting_fut;
+      }
+      return Future<T>::MakeFinished(*result);
+    }
+  }
+
+ private:
+  struct WrappedComp {
+    bool operator()(const Result<T>& left, const Result<T>& right) {
+      if (!left.ok() || !right.ok()) {
+        return false;
+      }
+      return compare(*left, *right);
+    }
+    Comp compare;
+  };
+
+  struct State {
+    State(AsyncGenerator<T> source, Comp compare, IsNext is_next, T initial_value)
+        : source(std::move(source)),
+          is_next(std::move(is_next)),
+          previous_value(std::move(initial_value)),
+          waiting_future(),
+          queue(WrappedComp{compare}),
+          finished(false),
+          mutex() {}
+
+    AsyncGenerator<T> source;
+    IsNext is_next;
+    T previous_value;
+    Future<T> waiting_future;
+    std::priority_queue<Result<T>, std::vector<Result<T>>, WrappedComp> queue;
+    bool finished;
+    util::Mutex mutex;
+  };
+
+  class Callback {
+   public:
+    explicit Callback(std::shared_ptr<State> state) : state_(std::move(state)) {}
+
+    void operator()(const Result<T> result) {
+      Future<T> to_deliver;
+      bool finished;
+      {
+        auto guard = state_->mutex.Lock();
+        bool ready_to_deliver = false;
+        if (!result.ok()) {
+          while (!state_->queue.empty()) {
+            state_->queue.pop();

Review comment:
       Nevermind.  Both `state_->queue = {}` and `swap` fail if the callables are lambdas because (I think) of lambda's deleted copy assignment operator.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org