EXAMPLES

Coroutine_context example

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

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

// 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_t;

    // create a coroutine
    copp::coroutine_context_default::ptr_t co_obj = coroutine_t::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() {
#if defined(UTIL_CONFIG_COMPILER_CXX_LAMBDAS) && UTIL_CONFIG_COMPILER_CXX_LAMBDAS
    // 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;
#else
    std::cerr << "lambda not supported, this sample is not available." << std::endl;
#endif
    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 <cstdio>
#include <cstring>
#include <ctime>
#include <inttypes.h>
#include <iostream>
#include <stdint.h>

// 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(NULL), 0);
}

int main() {
#if defined(UTIL_CONFIG_COMPILER_CXX_LAMBDAS) && UTIL_CONFIG_COMPILER_CXX_LAMBDAS
    // 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;
    }

#else
    std::cerr << "lambda not supported, this sample is not available." << std::endl;
#endif
    return 0;
}

Using stack pool

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

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

// 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 {
    typedef copp::allocator::stack_allocator_pool<stack_pool_t>  stack_allocator_t;
    typedef copp::coroutine_context_container<stack_allocator_t> coroutine_t;
};

// 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) && defined(UTIL_CONFIG_COMPILER_CXX_LAMBDAS) && UTIL_CONFIG_COMPILER_CXX_LAMBDAS

    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 << "lambda not supported, this sample is not available." << 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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <inttypes.h>
#include <stdint.h>
#include <vector>

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

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

#if defined(LIBCOTASK_MACRO_ENABLED) && defined(UTIL_CONFIG_COMPILER_CXX_LAMBDAS) && UTIL_CONFIG_COMPILER_CXX_LAMBDAS

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 and compiler support c++11");
    return 0;
}
#endif

Using copp::future::future_t and prepare for c++20 coroutine

This is a simple example of using copp::future::future_t<RESULT> and using copp::future::context<RESULT>:

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

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

// include manager header file
#include <libcopp/future/future.h>
#include <libcopp/future/context.h>

struct example_result_message_t {
    int return_code;
    int response_code;
};
struct example_poller;

typedef copp::future::result_t<
    example_result_message_t, // polled data if success
    int                       // error code if failed
> example_result_t;
typedef copp::future::future_t<example_result_t> example_future_t;
typedef copp::future::context_t<example_poller> example_context_t;

std::list<example_context_t*> g_executor;

struct example_poller {
    example_result_t::storage_type* result;
    std::list<example_context_t*>::iterator iter;
    example_poller(): result(NULL) {
        iter = g_executor.end();
    }
    ~example_poller() {
        // cleanup and remove from executor
        if (iter != g_executor.end()) {
            g_executor.erase(iter);
        }
    }
    void operator()(example_future_t& future, example_context_t &ctx) {
        if (NULL != result) {
            future.poll_data() = *result;
            result = NULL;
            // remove from executor
            if (iter != g_executor.end()) {
                g_executor.erase(iter);
                iter = g_executor.end();
            }
        } else {
            // add to executor
            if (iter == g_executor.end()) {
                iter = g_executor.insert(g_executor.end(), &ctx);
            }
        }
    }
};

void run_with_custom_context () {
    example_future_t future;
    example_context_t context;

    assert(future.is_ready() == false);

    // poll context for the first time and setup waker
    // future should only poll one context
    future.poll(context);

    while (!g_executor.empty()) {
        example_result_message_t msg;
        // set a result message
        msg.return_code = 0;
        msg.response_code = 200;
        // if both success type and error type is a small trivial type, storage_type will be result_t with union of success type and error type
        // else storage_type will be std::unique_ptr<result_t>
        // result_t::make_success(...) and result_t::make_error(...) will make sure to use the correct storage type
        example_result_t::storage_type result_storage = example_result_t::make_success(msg);
        (*g_executor.begin())->get_private_data().result = &result_storage;
        // just call context_t::wake to wakeup and poll again
        (*g_executor.begin())->wake();
    }

    // Then future is ready
    assert(future.is_ready() == true);
    example_result_t* result = future.data();
    assert(result != NULL);
    
    assert(result->is_success());
    assert(200 == result->get_success()->response_code);
    assert(false == result->is_error());
    assert(NULL == result->get_error());

    std::cout<< "Got future success response code: "<< result->get_success()->response_code<< std::endl;
}

static void custom_poller_function(copp::future::context_t<void> &, copp::future::context_t<void>::poll_event_data_t evt_data) {
    if (NULL == evt_data.private_data) {
        return;
    }

    example_future_t* future = reinterpret_cast<example_future_t*>(evt_data.future_ptr);

    example_result_message_t* msg = reinterpret_cast<example_result_message_t*>(evt_data.private_data);

    // if both success type and error type is a small trivial type, storage_type will be result_t with union of success type and error type
    // else storage_type will be std::unique_ptr<result_t>
    // result_t::make_success(...) and result_t::make_error(...) will make sure to use the correct storage type
    future->poll_data() = example_result_t::make_success(*msg);
}

void run_with_void_context () {
    example_future_t future;
    copp::future::context_t<void> context(copp::future::context_t<void>::construct(custom_poller_function));
    // upper code equal to:
    // copp::future::context_t<void> context;
    // context.set_poll_fn(custom_poller_function);

    assert(future.is_ready() == false);

    // poll context for the first time and setup waker
    // future should only poll one context
    future.poll(context);

    while (!future.is_ready()) {
        example_result_message_t msg;
        // set a result message
        msg.return_code = 0;
        msg.response_code = 200;
        context.set_private_data(reinterpret_cast<void*>(&msg));
        
        context.wake();
    }

    // Then future is ready
    assert(future.is_ready() == true);
    example_result_t* result = future.data();
    assert(result != NULL);
    
    assert(result->is_success());
    assert(200 == result->get_success()->response_code);
    assert(false == result->is_error());
    assert(NULL == result->get_error());

    std::cout<< "Got future success response code(context_t<void>): "<< result->get_success()->response_code<< std::endl;
}

int main() {
    run_with_custom_context();
    run_with_void_context();
    return 0;
}

Using c++20 coroutine

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

#include <iostream>

// include manager header file
#include <libcopp/future/std_coroutine_task.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

static copp::future::task_t<int> call_for_coroutine_task_with_int_result() {
    // ... any code
    co_return 123;
}

static copp::future::task_t<void> call_for_coroutine_task_with_void_result() {
    // ... any code
    co_return;
}

struct sample_task_waker_t;
typedef copp::future::task_t<int, sample_task_waker_t>    sample_task_t;
typedef copp::future::task_future_t<int>                  sample_future_t;
typedef copp::future::task_context_t<sample_task_waker_t> sample_task_context_t;

std::list<std::pair<sample_task_context_t *,int> > g_sample_executor;

struct sample_task_waker_t {
    std::list<std::pair<sample_task_context_t *,int> >::iterator refer_to;

    sample_task_waker_t() {
        refer_to = g_sample_executor.end();
    }

    ~sample_task_waker_t() {
        if (refer_to != g_sample_executor.end()) {
            g_sample_executor.erase(refer_to);
        }
    }

    void operator()(sample_future_t &fut, sample_task_context_t &ctx) {
        if (refer_to == g_sample_executor.end()) {
            // Add to custom executor when first polled
            refer_to = g_sample_executor.insert(
                g_sample_executor.end(), 
                std::make_pair(&ctx, 0)
            );
            return;
        }

        if (0 != (*refer_to).second) {
            fut.poll_data() = (*refer_to).second;
            // Because return type is a trivial type, we can just assign to value
            // It the return type is a trivial type, we can use 
            //     fut.poll_data() = copp::future::make_unique<T>(...);
            //   or 
            //     fut.poll_data() = std::make_unique<T>(...);
            //   to set the result data.
            g_sample_executor.erase(refer_to);
            refer_to = g_sample_executor.end();
        }
    }
};

static sample_task_t call_for_coroutine_task_with_custom_waker() {
    // suspend and wait custom waker
    (void)co_await LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE suspend_always();
    // ... any code
    // We can get the pointer to the future and th context of current task by co_yield current_future and current_context
    sample_future_t* future = co_yield sample_task_t::current_future();
    sample_task_context_t* context = co_yield sample_task_t::current_context();
    if (future && context && future->is_ready()) {
        // The return value will be ignored when the future is already set by custom waker
        std::cout<< "Coroutine: "<< context->get_task_id()<< " already got future data "<< (*future->data())<< " and will ignore co_return."<< std::endl;
    }
    co_return 123;
}

int main() {
    copp::future::task_t<int> t1 = call_for_coroutine_task_with_int_result();
    copp::future::task_t<void> t2 = call_for_coroutine_task_with_void_result();
    sample_task_t t3 = call_for_coroutine_task_with_custom_waker();
    std::cout<< "Coroutine t1: "<< t1.get_task_id()<< " -> "<< *t1.data()<< std::endl;
    std::cout<< "Coroutine t2: "<< t2.get_task_id()<< " -> "<< (t2.done()? "done": "running")<< std::endl;

    while (!g_sample_executor.empty()) {
        (*g_sample_executor.begin()).second = 456;
        (*g_sample_executor.begin()).first->wake();
    }

    std::cout<< "Coroutine t3: "<< t3.get_task_id()<< " -> "<< *t3.data()<< 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/future/std_coroutine_task.h>
#include <libcopp/future/std_coroutine_generator.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

struct sample_message_t {
    int ret_code;
    std::string response;
};

struct sample_generator_waker_t;

typedef copp::future::result_t<sample_message_t, int32_t> sample_result_t;
typedef copp::future::task_t<sample_result_t>             sample_task_t;
typedef copp::future::generator_future_t<sample_result_t> sample_future_t;
typedef copp::future::generator_context_t<sample_generator_waker_t> sample_generator_context_t;


std::list<std::pair<sample_generator_context_t *, std::string> > g_sample_executor;

struct sample_generator_waker_t {
    int32_t code;
    std::list<std::pair<sample_generator_context_t *, std::string> >::iterator refer_to;

    // All parameter passed into generator will be forward here
    sample_generator_waker_t(int32_t c) : code(c) {
        refer_to = g_sample_executor.end();
    }

    ~sample_generator_waker_t() {
        if (refer_to != g_sample_executor.end()) {
            g_sample_executor.erase(refer_to);
        }
    }

    void operator()(sample_future_t &fut, sample_generator_context_t &ctx) {
        if (refer_to == g_sample_executor.end()) {
            // Add to custom executor when first polled
            refer_to = g_sample_executor.insert(
                g_sample_executor.end(), 
                std::make_pair(&ctx, std::string())
            );
            return;
        }

        if (!(*refer_to).second.empty()) {
            // generator finished and produce a result message
            sample_message_t msg;
            msg.ret_code    = code;
            msg.response.swap((*refer_to).second);
            
            fut.poll_data() = sample_result_t::make_success(msg);
            // Because sample_result_t is not a trivial type, upper code is equal to these codes below:
            // auto ptr = std::make_unique<sample_result_t>(sample_result_t::create_success(std::move(msg)));
            // fut.poll_data() = std::move(ptr);

            g_sample_executor.erase(refer_to);
            refer_to = g_sample_executor.end();
        }
    }
};
typedef copp::future::generator_t<sample_result_t, sample_generator_waker_t> sample_generator_t;

static copp::future::task_t<void> call_for_noop_task() {
    co_return;
}

static copp::future::task_t<int> call_for_coroutine_task() {
    // We can start a subtask and await it
    copp::future::task_t<void> t = call_for_noop_task();
    (void)co_await t;

    sample_generator_t generator = copp::future::make_generator<sample_generator_t>(200);
    auto result = co_await generator;

    if (result) {
        if (result->is_success()) {
            std::cout<< "Got response message: "<< result->get_success()->response<< std::endl;
            co_return result->get_success()->ret_code;    
        } else {
            co_return *result->get_error();
        }
    }
    co_return 0;
}

int main() {
    copp::future::task_t<int> t = call_for_coroutine_task();
    assert(false == t.done());
    assert(NULL == t.data());  // Task isn't finished and has no data

    while (!g_sample_executor.empty()) {
        // async jobs finished and wake coroutine here
        g_sample_executor.begin()->second = "Hello World!";
        g_sample_executor.begin()->first->wake();
    }

    assert(t.done());
    assert(t.data()); // Task is finished and has data
    std::cout<< "Task "<< t.get_task_id()<< " finished and got result: "<< *t.data()<< std::endl;
    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) when polling c++20 coroutine task or generator

By add context into custom executor or manager and remove it when destroyed, we can add more flexible error handling or procedure.

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

#include <iostream>

// include manager header file
#include <libcopp/future/std_coroutine_task.h>
#include <libcopp/future/std_coroutine_generator.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

// ============================ types for task and generator ============================
class sample_message_t {
private:
    sample_message_t(const sample_message_t &)
        UTIL_CONFIG_DELETED_FUNCTION;
    sample_message_t &
    operator=(const sample_message_t &) UTIL_CONFIG_DELETED_FUNCTION;
    sample_message_t(sample_message_t &&)
        UTIL_CONFIG_DELETED_FUNCTION;
    sample_message_t &
    operator=(sample_message_t &&) UTIL_CONFIG_DELETED_FUNCTION;

public:
    int ret_code;
    int rsp_code;

    sample_message_t() : ret_code(0), rsp_code(0) {}
    sample_message_t(int c) : ret_code(c), rsp_code(c) {}
    ~sample_message_t() {}
};

#define SAMPLE_TIMEOUT_ERROR_CODE (-500)

struct sample_generator_waker_t;
struct sample_task_waker_t;

typedef copp::future::result_t<sample_message_t, int32_t>           sample_result_t;
typedef copp::future::task_t<sample_result_t, sample_task_waker_t>  sample_task_t;
typedef copp::future::generator_context_t<sample_generator_waker_t> sample_generator_context_t;
typedef copp::future::generator_future_t<sample_result_t>           sample_generator_future_t;
typedef copp::future::poll_t<sample_result_t>                       sample_poll_t;
typedef copp::future::task_context_t<sample_task_waker_t>           sample_task_context_t;
typedef copp::future::task_future_t<sample_result_t>                sample_task_future_t;

std::list<sample_generator_context_t *> g_sample_generator_waker_list;
std::list<sample_task_context_t *>      g_sample_task_waker_list;

struct sample_generator_waker_t {
    int32_t                                                    code;
    int32_t                                                    await_times;
    std::list<sample_generator_context_t *>::iterator refer_to;
    sample_generator_waker_t(int32_t c, int32_t at) : code(c), await_times(at) {
        refer_to = g_sample_generator_waker_list.end();
    }

    ~sample_generator_waker_t() {
        // Remove from executor when destroyed
        if (refer_to != g_sample_generator_waker_list.end()) {
            g_sample_generator_waker_list.erase(refer_to);
        }
    }

    void operator()(sample_generator_future_t &fut, sample_generator_context_t &ctx) {
        // Remove from executor when polled
        if (refer_to != g_sample_generator_waker_list.end()) {
            g_sample_generator_waker_list.erase(refer_to);
            refer_to = g_sample_generator_waker_list.end();
        }
        if (await_times-- > 0) {
            // Add to executor when not ready
            refer_to = g_sample_generator_waker_list.insert(
                g_sample_generator_waker_list.end(), &ctx);
            return;
        }

        if (code > 0) {
            fut.poll_data() = sample_result_t::make_success(code);
        } else {
            fut.poll_data() = sample_result_t::make_error(code);
        }
    }
};

struct sample_task_waker_t {
    int32_t                                               code;
    std::list<sample_task_context_t *>::iterator refer_iter;

    template <class... TARGS>
    sample_task_waker_t(TARGS &&...)
        : code(0), refer_iter(g_sample_task_waker_list.end()) {}
    ~sample_task_waker_t() {
        // Remove from executor when destroyed
        if (g_sample_task_waker_list.end() != refer_iter) {
            g_sample_task_waker_list.erase(refer_iter);
        }
    }

    void operator()(sample_task_future_t &fut, sample_task_context_t &ctx) {
        // Remove from executor when polled
        if (g_sample_task_waker_list.end() != refer_iter) {
            g_sample_task_waker_list.erase(refer_iter);
            refer_iter = g_sample_task_waker_list.end();
        }

        if (0 != code) {
            fut.poll_data() = sample_result_t::make_error(code);
            return;
        }

        // Add to executor when not ready
        refer_iter = g_sample_task_waker_list.insert(
            g_sample_task_waker_list.end(), &ctx);
    }
};

// ============================ timeout for generator ============================
typedef copp::future::generator_t<sample_result_t, sample_generator_waker_t> sample_generator_t;
static sample_task_t call_for_await_generator_and_timeout(int32_t await_times) {
    std::cout<< "ready to co_await generator." << std::endl;

    auto          gen = copp::future::make_generator<sample_generator_t>(200, await_times);
    sample_poll_t ret;
    auto await_fut = co_await gen;

    ret = std::move(gen.poll_data());

    // the return is still pending, because it's resumed by timeout waker
    std::cout<< "\tret.is_pending() :"<< ret.is_pending()<< ", await_fut:"<< await_fut<< std::endl;
    sample_task_context_t *context = co_yield sample_task_t::current_context();
    if (context) {
        std::cout<< "\texpected code: "<< SAMPLE_TIMEOUT_ERROR_CODE<< ", real is: "<< context->get_private_data().code<< std::endl;;
    }

    sample_task_future_t *fut = co_yield sample_task_t::current_future();
    if (fut) {
        std::cout<< "\tfut->is_ready() :"<< fut->is_ready() << std::endl;
        if (fut->data() && fut->data()->is_error()) {
            std::cout<< "\texpected code: "<< SAMPLE_TIMEOUT_ERROR_CODE<< ", real is: "<< *fut->data()->get_error()<< std::endl;;
        }
    }

    co_return sample_result_t::make_error(-1);
}

static void poll_generator_and_timeout() {
    sample_task_t t1 = call_for_await_generator_and_timeout(3);

    // set timeout result
    for (int retry_times = 10; retry_times >= 0 && !g_sample_task_waker_list.empty(); --retry_times) {
        (*g_sample_task_waker_list.begin())->get_private_data().code = SAMPLE_TIMEOUT_ERROR_CODE;
        // wake and resume task
        (*g_sample_task_waker_list.begin())->wake();
    }

    std::cout<< "task status should be DONE(" << ((int)sample_task_t::status_type::DONE)<< "), real is: " << ((int)t1.get_status())<< std::endl;
    if (t1.data() && t1.data()->is_error()) {
        std::cout<< "\texpected code: "<< SAMPLE_TIMEOUT_ERROR_CODE<< ", real is: "<< *t1.data()->get_error()<< std::endl;
    }

    // cleanup generator dispatcher
    for (int retry_times = 10; retry_times >= 0 && !g_sample_generator_waker_list.empty();
         --retry_times) {
        (*g_sample_generator_waker_list.begin())->wake();
    }

    // cleanup task dispatcher
    for (int retry_times = 10; retry_times >= 0 && !g_sample_generator_waker_list.empty(); --retry_times) {
        (*g_sample_generator_waker_list.begin())->wake();
    }
}

// ============================ timeout for task ============================
static sample_task_t call_for_await_task_and_timeout(int32_t await_times) {
    std::cout<< "ready to co_await task." << std::endl;

    auto t_dep = call_for_await_generator_and_timeout(await_times);
    auto t_fut = co_await t_dep;

    // the return is still pending, because it's resumed by timeout waker
    std::cout<< "\tt_dep.done() :"<< t_dep.done() << ", t_fut: "<< t_fut<<std::endl;
    sample_task_context_t *context = co_yield sample_task_t::current_context();
    if (context) {
        std::cout<< "\texpected code: "<< SAMPLE_TIMEOUT_ERROR_CODE<< ", real is: "<< context->get_private_data().code<< std::endl;;
    }

    sample_task_future_t *fut = co_yield sample_task_t::current_future();
    if (fut) {
        std::cout<< "\tfut->is_ready() :"<< fut->is_ready() << std::endl;
        if (fut->data() && fut->data()->is_error()) {
            std::cout<< "\texpected code: "<< SAMPLE_TIMEOUT_ERROR_CODE<< ", real is: "<< *fut->data()->get_error()<< std::endl;;
        }
    }

    co_return sample_result_t::make_error(-1);
}

static void poll_no_trival_task_and_timeout() {
    sample_task_t t1 = call_for_await_task_and_timeout(3);

    // set timeout result
    for (int retry_times = 10; retry_times >= 0 && !t1.done() && t1.get_context(); --retry_times) {
        t1.get_context()->get_private_data().code = SAMPLE_TIMEOUT_ERROR_CODE;
        // wake and resume task
        t1.get_context()->wake();
    }

    std::cout<< "t1.done() :"<< t1.done() << std::endl;
    std::cout<< "\ttask "<< t1.get_task_id() <<" status should be DONE(" << ((int)sample_task_t::status_type::DONE)<< "), real is: " << ((int)t1.get_status())<< std::endl;
    if (t1.data() && t1.data()->is_error()) {
        std::cout<< "\texpected code: "<< SAMPLE_TIMEOUT_ERROR_CODE<< ", real is: "<< *t1.data()->get_error()<< std::endl;
    }

    // cleanup task dispatcher
    for (int retry_times = 10; retry_times >= 0 && !g_sample_task_waker_list.empty(); --retry_times) {
        (*g_sample_task_waker_list.begin())->get_private_data().code = SAMPLE_TIMEOUT_ERROR_CODE;
        (*g_sample_task_waker_list.begin())->wake();
    }


    // cleanup generator dispatcher
    for (int retry_times = 10; retry_times >= 0 && !g_sample_generator_waker_list.empty();
         --retry_times) {
        (*g_sample_generator_waker_list.begin())->wake();
    }
}

int main() {
    // sample for await generator and timeout
    poll_generator_and_timeout();

    // sample for await task and timeout
    poll_no_trival_task_and_timeout();
    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/future/std_coroutine_task.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::future::task_t<int> call_for_await_cotask(my_task_t::ptr_t t) {
    if (t) {
        co_return co_await t;
    }

    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));

    if (nullptr != t.data()) {
        std::cout<< "co_await a cotask::task and get result: "<< *t.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 && defined(UTIL_CONFIG_COMPILER_CXX_LAMBDAS) && UTIL_CONFIG_COMPILER_CXX_LAMBDAS
// include task header file
#include <libcotask/task.h>

struct my_task_macro_t {
    typedef copp::coroutine_fiber_context_default::allocator_type stack_allocator_t;
    typedef copp::coroutine_fiber_context_default                 coroutine_t;
};

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 = NULL;
std::string g_msvc_debuger_pattern;

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

    if ((hFile != NULL) && (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, NULL);
        CloseHandle(hFile);
    }
}

LONG WINAPI GPTUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) {
    //得到当前时间
    SYSTEMTIME st;
    ::GetLocalTime(&st);
    //得到程序所在文件夹
    // TCHAR exeFullPath[256]; // MAX_PATH
    // GetModuleFileName(NULL, 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 (NULL == 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 = NULL;
    }
}
#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