Overwriting first element of a vector changes contents of last element of vector

Viewed 168

When writing my code, I noticed that running the code returns incorrect results, it turns out something in my code is changing the vector of handles for my coroutines and narrowed it down to one line of code where I overwrite an existing element of the handle vector with a new element.

Doing that also changes the content of the last element of the vector (more specifically, the bool from the myTask header) but not the elements in between.

Does anyone know what causes this? Any help appreciated.

Full implementation code:

#include <concepts>
#include <coroutine>
#include <exception>
#include <iostream>
#include <myTask.h>
#include <vector>

myTask<int> getVectorInt(std::vector<int>& array, int key, bool interleave)
{
  std::cout << "started lookup of key: " << key << std::endl;
  int result = array.at(key);
  if (interleave == true)
  {
    std::cout << "about to suspend task with key: " << key << std::endl;
    co_await std::suspend_always{};
    std::cout << "resumed task with key: " << key << std::endl;
  }
  co_return result;
}

void interleavedExecution(std::vector<int>& lookup, std::vector<int>& keys, std::vector<int>& results)
{
  // group size = number of concurrent instruction streams
  int groupsize = 3;

  // initialization of handle vector
  std::vector<std::coroutine_handle<myTask<int>::promise_type>> handles;

  // initialization of promise vector
  std::vector<myTask<int>::promise_type> promises;

  // creating/initializing first handles
  for (int i = 0; i < groupsize; ++i)
  {
    handles.push_back(getVectorInt(lookup, keys.at(i), true));
  }

  int notDone = groupsize;
  int i = groupsize;

  // interleaved execution starts here
  while (notDone > 0)
  {
    for (int handleIndex = 0; handleIndex < handles.size(); ++handleIndex)
    {
      if (!handles.at(handleIndex).promise().isDone())
      {
        handles.at(handleIndex).resume();  
        handles.at(handleIndex).promise().boolIsDone = true; 
      }
      else 
      {
        // pushing value back directly into results
        results.push_back(handles.at(handleIndex).promise().value_);
       
        if (i < keys.size())
        {
          // bug here, changes the last boolIsDone also to false (or messes with the last vector element)
          handles.at(handleIndex) = getVectorInt(lookup, keys.at(i), true);
          
          handles.at(handleIndex).promise().boolIsDone = false;
          ++i;
        }
        else { --notDone; }
      }
    }
  }
}

template <typename T> 
void outputVector(std::vector<T> toOutput)
{
  std::cout << "Results: ";
  for (int i = 0; i < toOutput.size(); ++i)
  {
    std::cout << toOutput.at(i) << ' ';
  }
}

int main()
{
  std::vector<int> lookup = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
  std::vector<int> keys = {4, 2, 0, 6, 9, 0};
  std::vector<int> results;

  // correct output: 50, 30, 10, 70, 100, 10
  // given output: 50, 30, 70, 10, 100, 10
  interleavedExecution(lookup, keys, results);
  outputVector(results);
}

myTask header carrying a bool:

#include <concepts>
#include <coroutine>
#include <exception>
#include <iostream>

template <typename T>
struct myTask {
  struct promise_type {
    unsigned value_;
    ~promise_type() {
      //std::cout << "promise_type destroyed" << std::endl;
    }

    myTask<T> get_return_object() {
      return myTask<T> {
        .h_ = std::coroutine_handle<promise_type>::from_promise(*this)
      };
    }
    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() { return {}; }
    void unhandled_exception() { std::terminate(); }

    std::suspend_always return_value(unsigned value) {
      value_ = value;
      return {};
    }

    bool boolIsDone = false;

    auto isDone() { return boolIsDone; }
  };

  std::coroutine_handle<promise_type> h_;
  operator std::coroutine_handle<promise_type>() const { 
    //std::cout << "called handle" << std::endl;
    return h_; }

  
};
1 Answers

It turned out that changing the return type of final_suspend() from std::suspend_never to std::suspend_always fixed the issue.

Related