EXAMPLES

Coroutine_context example

This is a simple example of using basic coroutine context below:

#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <iostream>

// include context header file
#include <libcopp/coroutine/coroutine_context_container.h>

// define a coroutine runner
int my_runner(void *) {
  copp::coroutine_context *addr = copp::this_coroutine::get_coroutine();

  std::cout << "cortoutine " << addr << " is running." << std::endl;

  addr->yield();

  std::cout << "cortoutine " << addr << " is resumed." << std::endl;

  return 1;
}

int main() {
  typedef copp::coroutine_context_default coroutine_type;

  // create a coroutine
  copp::coroutine_context_default::ptr_t co_obj = coroutine_type::create(my_runner);
  std::cout << "cortoutine " << co_obj << " is created." << std::endl;

  // start a coroutine
  co_obj->start();

  // yield from my_runner
  std::cout << "cortoutine " << co_obj << " is yield." << std::endl;
  co_obj->resume();

  std::cout << "cortoutine " << co_obj << " exit and return " << co_obj->get_ret_code() << "." << std::endl;
  return 0;
}

Also, you can use copp::coroutine_context_container<ALLOCATOR> instead of copp::coroutine_context_default to use a different stack allocator.

Coroutine task example

This is a simple example of using coroutine task with lambda expression:

#include <iostream>

// include task header file
#include <libcotask/task.h>

typedef cotask::task<> my_task_t;

int main() {
  // create a task using factory function [with lambda expression]
  my_task_t::ptr_t task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
    return 0;
  });

  std::cout << "task " << task->get_id() << " created" << std::endl;
  // start a task
  task->start();

  std::cout << "task " << task->get_id() << " yield" << std::endl;
  task->resume();
  std::cout << "task " << task->get_id() << " stoped, ready to be destroyed." << std::endl;
  return 0;
}

Also, you can your stack allocator or id allocator by setting different parameters in template class cotask::task<TCO_MACRO>

Using coroutine task manager

This is a simple example of using task manager:

#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>

// include context header file
#include <libcotask/task.h>
#include <libcotask/task_manager.h>

// create a task manager
typedef cotask::task<> my_task_t;
typedef my_task_t::ptr_t task_ptr_type;
typedef cotask::task_manager<my_task_t> mgr_t;
mgr_t::ptr_t task_mgr = mgr_t::create();

// If you task manager to manage timeout, it's important to call tick interval

void tick() {
  // the first parameter is second, and the second is nanosecond
  task_mgr->tick(time(nullptr), 0);
}

int main() {
  // create two coroutine task
  task_ptr_type co_task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
    return 0;
  });
  task_ptr_type co_another_task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
    return 0;
  });

  int res = task_mgr->add_task(co_task, 5, 0);  // add task and setup 5s for timeout
  if (res < 0) {
    std::cerr << "some error: " << res << std::endl;
    return res;
  }

  res = task_mgr->add_task(co_another_task);  // add task without timeout
  if (res < 0) {
    std::cerr << "some error: " << res << std::endl;
    return res;
  }

  res = task_mgr->start(co_task->get_id());
  if (res < 0) {
    std::cerr << "start task " << co_task->get_id() << " failed, error code: " << res << std::endl;
  }

  res = task_mgr->start(co_another_task->get_id());
  if (res < 0) {
    std::cerr << "start task " << co_another_task->get_id() << " failed, error code: " << res << std::endl;
  }

  res = task_mgr->resume(co_task->get_id());
  if (res < 0) {
    std::cerr << "resume task " << co_task->get_id() << " failed, error code: " << res << std::endl;
  }

  res = task_mgr->kill(co_another_task->get_id());
  if (res < 0) {
    std::cerr << "kill task " << co_another_task->get_id() << " failed, error code: " << res << std::endl;
  } else {
    std::cout << "kill task " << co_another_task->get_id() << " finished." << std::endl;
  }

  return 0;
}

Using stack pool

This is a simple example of using stack pool for cotask:

#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>

// include context header file
#include <libcopp/stack/stack_pool.h>
#include <libcotask/task.h>

// define the stack pool type
typedef copp::stack_pool<copp::allocator::default_statck_allocator> stack_pool_t;

// define how to create coroutine context
struct sample_macro_coroutine {
  using stack_allocator_type = copp::allocator::stack_allocator_pool<stack_pool_t>;
  using coroutine_type = copp::coroutine_context_container<stack_allocator_type>;
  using value_type = int;
};

// create a stack pool
static stack_pool_t::ptr_t global_stack_pool = stack_pool_t::create();

typedef cotask::task<sample_macro_coroutine> sample_task_t;

int main() {
#if defined(LIBCOTASK_MACRO_ENABLED)

  global_stack_pool->set_min_stack_number(4);
  std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
            << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
            << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
            << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
  // create two coroutine task
  {
    copp::allocator::stack_allocator_pool<stack_pool_t> alloc(global_stack_pool);
    sample_task_t::ptr_t co_task = sample_task_t::create(
        []() {
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " started" << std::endl;
          cotask::this_task::get_task()->yield();
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " resumed" << std::endl;
          return 0;
        },
        alloc);

    if (!co_task) {
      std::cerr << "create coroutine task with stack pool failed" << std::endl;
      return 0;
    }

    std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
              << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
              << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
              << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;

    // ..., then do anything you want to do with these tasks
  }

  std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
            << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
            << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
            << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;

  {
    copp::allocator::stack_allocator_pool<stack_pool_t> alloc(global_stack_pool);
    sample_task_t::ptr_t co_another_task = sample_task_t::create(
        []() {
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " started" << std::endl;
          cotask::this_task::get_task()->yield();
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " resumed" << std::endl;
          return 0;
        },
        alloc);

    if (!co_another_task) {
      std::cerr << "create coroutine task with stack pool failed" << std::endl;
      return 0;
    }

    // ..., then do anything you want to do with these tasks
  }

  std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
            << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
            << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
            << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
#else
  std::cerr << "this sample require cotask enabled." << std::endl;
#endif
  return 0;
}

Using then or await_task

This is a simple example of using then and await_task for cotask:

/*
 * sample_readme_5.cpp
 *
 *  Created on: 2014-05-19
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <vector>

#include <libcopp/utils/std/explicit_declare.h>

// include manager header file
#include <libcotask/task.h>

#if defined(LIBCOTASK_MACRO_ENABLED)

typedef cotask::task<> my_task_t;

int main() {
  int test_code = 128;

  // create a task using lambda expression
  my_task_t::ptr_t first_task = my_task_t::create([&]() {
    puts("|first task running and will be yield ...");
    cotask::this_task::get_task()->yield();
    puts("|first task resumed ...");
    printf("test code already reset => %d\n", ++test_code);
  });

  // add many then task using lambda expression
  first_task
      ->then([=]() {
        puts("|second task running...");
        printf("test code should be inited 128 => %d\n", test_code);
      })
      ->then([&]() {
        puts("|haha ... this is the third task.");
        printf("test code is the same => %d\n", ++test_code);
        return "return value will be ignored";
      })
      ->then(
          [&](EXPLICIT_UNUSED_ATTR void *priv_data) {
            puts("|it's boring");
            printf("test code is %d\n", ++test_code);
            assert(&test_code == priv_data);
            return 0;
          },
          &test_code);

  test_code = 0;
  // start a task
  first_task->start();
  first_task->resume();

  // these code below will failed.
  first_task->then([]() {
    puts("this will run immediately.");
    return 0;
  });

  my_task_t::ptr_t await_task = my_task_t::create([&]() {
    puts("await_task for first_task.");
    return 0;
  });
  await_task->await_task(first_task);

  printf("|task start twice will failed: %d\n", first_task->start());
  printf("|test_code end with %d\n", test_code);
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled");
  return 0;
}
#endif

Using c++20 coroutine

// Copyright 2023 owent
// Created by owent on 2022-05-27

#include <iostream>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

static copp::callable_future<int> coroutine_callable_with_int_result() {
  // ... any code
  co_return 123;
}

static copp::callable_future<void> coroutine_callable_with_void_result() {
  // ... any code
  co_return;
}

static copp::callable_future<void> coroutine_simulator_task() {
  // suspend and wait custom waker
  (void)co_await LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE suspend_always();
  // ... any code
  // We can get current status by co_yield yield_status()
  auto current_status = co_yield copp::callable_future<int>::yield_status();
  // The return value will be ignored when the future is already set by custom waker
  std::cout << "Current coroutine callable status: " << static_cast<uint32_t>(current_status) << std::endl;

  co_await coroutine_callable_with_void_result();

  int result = co_await coroutine_callable_with_int_result();
  std::cout << "Coroutine int callable result: " << result << std::endl;
  co_return;
}

int main() {
  auto rpc_result = coroutine_simulator_task();

  // We should not explict call start and get_internal_handle().resume() in a real usage
  // It's only allowed to start and resume by co_wait the callable_future object
  rpc_result.get_internal_handle().resume();  // resume co_await LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE suspend_always();

  std::cout << "Current coroutine callable status: " << static_cast<uint32_t>(rpc_result.get_status()) << std::endl;
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Using c++20 coroutine with custom generator

/*
 * sample_readme_8.cpp
 *
 *  Created on: 2020-05-22
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <assert.h>
#include <iostream>
#include <string>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

using my_generator = copp::generator_future<int>;
std::list<my_generator::context_pointer_type> g_sample_executor;

static void generator_callback(my_generator::context_pointer_type ctx) {
  g_sample_executor.emplace_back(std::move(ctx));
}

static copp::callable_future<void> coroutine_simulator_rpc() {
  my_generator generator_object{generator_callback};
  auto value1 = co_await generator_object;
  std::cout << "co_await named generator: " << value1 << std::endl;
  auto value2 = co_await my_generator{generator_callback};
  std::cout << "co_await temporary generator: " << value2 << std::endl;

  generator_object.get_context()->reset_value();
  auto value3 = co_await generator_object;
  std::cout << "reset and co_await named generator again: " << value3 << std::endl;
  co_return;
}

int main() {
  int result = 191;
  auto f = coroutine_simulator_rpc();

  while (!g_sample_executor.empty()) {
    auto ctx = g_sample_executor.front();
    g_sample_executor.pop_front();
    ctx->set_value(++result);
  }
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Custom error (timeout for example) for c++20 coroutine

By implementing std_coroutine_default_error_transform<CustomType> , we can transform error code of libcopp to our custom type.

/*
 * sample_readme_9.cpp
 *
 *  Created on: 2020-05-20
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <iostream>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

// ============================ types for task and generator ============================
class sample_message_t {
 public:
  int ret_code;

  sample_message_t() : ret_code(0) {}
  sample_message_t(int c) : ret_code(c) {}
  sample_message_t(const sample_message_t &) = default;
  sample_message_t &operator=(const sample_message_t &) = default;
  sample_message_t(sample_message_t &&) = default;
  sample_message_t &operator=(sample_message_t &&) = default;
  ~sample_message_t() {}
};

#  define SAMPLE_TIMEOUT_ERROR_CODE (-500)

LIBCOPP_COPP_NAMESPACE_BEGIN
template <>
struct std_coroutine_default_error_transform<sample_message_t> {
  using type = sample_message_t;
  type operator()(promise_status in) const {
    if (in == promise_status::kTimeout) {
      return sample_message_t{SAMPLE_TIMEOUT_ERROR_CODE};
    }
    return sample_message_t{static_cast<int>(in)};
  }
};
LIBCOPP_COPP_NAMESPACE_END

using int_generator = copp::generator_future<int>;
std::list<int_generator::context_pointer_type> g_int_executor;
using custom_generator = copp::generator_future<sample_message_t>;
std::list<custom_generator::context_pointer_type> g_sample_executor;

static void int_generator_callback(int_generator::context_pointer_type ctx) {
  g_int_executor.emplace_back(std::move(ctx));
}

static void custom_generator_callback(custom_generator::context_pointer_type ctx) {
  g_sample_executor.emplace_back(std::move(ctx));
}

static copp::callable_future<int> coroutine_simulator_rpc_integer_l2() {
  auto result = co_await int_generator{int_generator_callback};
  co_return result;
}

static copp::callable_future<void> coroutine_simulator_rpc_integer() {
  auto result = co_await coroutine_simulator_rpc_integer_l2();
  std::cout << "int generator is killed with code: " << result << std::endl;
  co_return;
}

static copp::callable_future<int> coroutine_simulator_rpc_custom_l2() {
  auto result = co_await custom_generator{custom_generator_callback};
  co_return result.ret_code;
}

static copp::callable_future<void> coroutine_simulator_rpc_custom() {
  auto result = co_await coroutine_simulator_rpc_custom_l2();
  std::cout << "custom generator is killed with code: " << result << std::endl;
  co_return;
}

int main() {
  // sample for await generator and timeout
  auto f1 = coroutine_simulator_rpc_integer();
  f1.kill(copp::promise_status::kCancle, true);
  std::cout << "int generator is killed" << std::endl;

  // sample for await task and timeout
  auto f2 = coroutine_simulator_rpc_custom();
  f2.kill(copp::promise_status::kTimeout, true);
  std::cout << "custom generator is killed" << std::endl;
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Let c++20 coroutine work with cotask::task<MACRO>

This is a simple example to let c++20 coroutine await cotask::task

/*
 * sample_readme_10.cpp
 *
 *  Created on: 2020-05-20
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <iostream>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcotask/task.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

typedef cotask::task<> my_task_t;

static copp::callable_future<int> call_for_await_cotask(my_task_t::ptr_t t) {
  if (t) {
    auto ret = co_await t;
    co_return ret;
  }

  co_return 0;
}

static int cotask_action_callback(void*) {
  int ret = 234;
  void* ptr = nullptr;
  cotask::this_task::get_task()->yield(&ptr);
  if (ptr != nullptr) {
    ret = *reinterpret_cast<int*>(ptr);
  }
  return ret;
}

int main() {
  my_task_t::ptr_t co_task = my_task_t::create(cotask_action_callback);

  auto t = call_for_await_cotask(co_task);
  co_task->start();

  int res = 345;
  co_task->resume(reinterpret_cast<void*>(&res));

  std::cout << "co_await a cotask::task and get result: " << t.get_internal_promise().data() << std::endl;
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Using SetUnhandledExceptionFilter on Windows with cotask::task<MACRO>

Some applications will use SetUnhandledExceptionFilter to catch unhandled exception and analysis crash problem. But SetUnhandledExceptionFilter is only works with coroutine context of windows fiber . This is a sample of using windows fiber as coroutine context in cotask::task<MACRO> .

#include <iostream>

#include <libcopp/utils/config/libcopp_build_features.h>

#if (defined(LIBCOTASK_MACRO_ENABLED) && LIBCOTASK_MACRO_ENABLED) && defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && \
    LIBCOPP_MACRO_ENABLE_WIN_FIBER
// include task header file
#  include <libcotask/task.h>

struct my_task_macro_t {
  using stack_allocator_type = copp::coroutine_fiber_context_default::allocator_type;
  using coroutine_type = copp::coroutine_fiber_context_default;
  using value_type = int;
};

typedef cotask::task<my_task_macro_t> my_task_t;

#  ifdef _MSC_VER
#    pragma warning(push)
#    pragma warning(disable : 4091)

#    include <atlconv.h>
#    include <imagehlp.h>

#    pragma comment(lib, "dbghelp.lib")

#    ifdef UNICODE
#      define SAMPLE_VC_TEXT(x) A2W(x)
#    else
#      define SAMPLE_VC_TEXT(x) x
#    endif

LPTOP_LEVEL_EXCEPTION_FILTER g_msvc_debuger_old_handle = nullptr;
std::string g_msvc_debuger_pattern;

inline void CreateMiniDump(EXCEPTION_POINTERS *pep, LPCTSTR strFileName) {
  HANDLE hFile =
      CreateFile(strFileName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

  if ((hFile != nullptr) && (hFile != INVALID_HANDLE_VALUE)) {
    MINIDUMP_EXCEPTION_INFORMATION mdei;
    mdei.ThreadId = GetCurrentThreadId();
    mdei.ExceptionPointers = pep;
    mdei.ClientPointers = FALSE;
    // MINIDUMP_CALLBACK_INFORMATION mci;
    // mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
    // mci.CallbackParam = 0;
    MINIDUMP_TYPE mdt =
        (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | MiniDumpWithDataSegs | MiniDumpWithHandleData |
                        MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules);
    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 0) ? &mdei : 0, 0, nullptr);
    CloseHandle(hFile);
  }
}

LONG WINAPI GPTUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) {
  //得到当前时间
  SYSTEMTIME st;
  ::GetLocalTime(&st);
  //得到程序所在文件夹
  // TCHAR exeFullPath[256]; // MAX_PATH
  // GetModuleFileName(nullptr, exeFullPath, 256);//得到程序模块名称,全路径

  TCHAR szFileName[_MAX_FNAME] = {0};

  USES_CONVERSION;

  wsprintf(szFileName, TEXT("%s-%04d-%02d-%02d.%02d%02d%02d.%03d.dmp"), SAMPLE_VC_TEXT(g_msvc_debuger_pattern.c_str()),
           st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
  CreateMiniDump(pExceptionInfo, szFileName);

  if (nullptr == g_msvc_debuger_old_handle) {
    return EXCEPTION_EXECUTE_HANDLER;  // 下一个Handle, 一般是程序停止运行
  }

  return g_msvc_debuger_old_handle(pExceptionInfo);
}

void __cdecl sample_setup_msvc_mini_dump(const char *prefix) {
  g_msvc_debuger_pattern = prefix;
  g_msvc_debuger_old_handle = SetUnhandledExceptionFilter(GPTUnhandledExceptionFilter);
  if (g_msvc_debuger_old_handle == GPTUnhandledExceptionFilter) {
    g_msvc_debuger_old_handle = nullptr;
  }
}
#  endif

int main() {
#  ifdef _MSC_VER
  sample_setup_msvc_mini_dump("d:/libcopp-test-minidump");
#  endif
  // create a task using factory function [with lambda expression]
  my_task_t::ptr_t task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;

    // ! Make crash and it's will generate a mini dump into d:/libcopp-test-minidump-*.dmp
    // copp::this_coroutine::get_coroutine()->yield();
    return 0;
  });

  std::cout << "task " << task->get_id() << " created" << std::endl;
  // start a task
  task->start();

  std::cout << "task " << task->get_id() << " yield" << std::endl;
  task->resume();
  std::cout << "task " << task->get_id() << " stoped, ready to be destroyed." << std::endl;

  return 0;
}

#  ifdef _MSC_VER
#    pragma warning(pop)
#  endif

#else
int main() {
  std::cerr << "lambda not supported, or fiber is not supported, this sample is not available." << std::endl;
  return 0;
}
#endif