#pragma once /* * Copyright (c) 2016 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2020-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ #include #include #include #include namespace maxscale { // Internal, not intended for public consumption. class future_internal { public: virtual ~future_internal() {} }; /** * The class template maxscale::future provides a mechanism to access the result of * asynchronous operations. It is based upon C++11's std::future as documented here: * http://en.cppreference.com/w/cpp/thread/future * * std::future uses C++11's rvalue references, which are not available on the * environments where MaxScale is compiled. Consequently, some care is needed * when using maxscale::future so that unintended copying does not occur. * * When C++11 is available, it should be straightforward to take the std::future * into use. */ template class future { public: /** * Constructs a future with no shared state. */ future() { } /** * Move constructor * * @note This looks like a regular copy-constructor, but should be treated as * a move constructor. * * @param other The future to move. After the call, @c other will not refer * to a future result. */ future(const future& other) : m_sInternal(other.m_sInternal) { other.m_sInternal.reset(); } /** * Move a future. * * @note This looks like a regular assignment operator, but the assigned value * is moved. * * @param rhs The future to move. After the call, @c rhs will not refer * to a future result. * @return *this */ future& operator = (const future& rhs) { future copy(rhs); copy.swap(*this); return *this; } /** * Destructor */ ~future() { } /** * Swap the content * * @param rhs The future to swap the contents with. */ void swap(future& rhs) { std::swap(m_sInternal, rhs.m_sInternal); } /** * Checks if the future refers to a shared state * * @return True, if this future refers to shared state, false otherwise. */ bool valid() const { return m_sInternal.get() != NULL; } /** * Waits until the future has a valid result and returns it. * * @note After the function returns, the future will no longer be valid. * * @return The stored value. */ T get() { ss_dassert(valid()); if (valid()) { T rv = m_sInternal->get(); m_sInternal.reset(); return rv; } else { MXS_ERROR("Get called on non-valid future."); return T(); } } /** * Blocks until the result becomes available. * * @note Only a valid future can be waited for. */ void wait() const { ss_dassert(valid()); if (valid()) { m_sInternal->wait(); } else { MXS_ERROR("An attempt to wait on a non-valid future."); } } public: class internal : public future_internal { public: internal() : m_t() , m_waited(false) { } ~internal() { } T get() { wait(); return m_t; } void set(T t) { m_t = t; m_sem.post(); } void wait() const { if (!m_waited) { m_sem.wait(); m_waited = true; } } private: T m_t; mutable bool m_waited; Semaphore m_sem; }; future(std::shared_ptr sInternal) : m_sInternal(sInternal) { } private: mutable std::shared_ptr m_sInternal; }; /** * The class template std::packaged_task wraps a function so that it can be called * asynchronously. It is based upon C++11 std::packaged_task as documented here: * http://en.cppreference.com/w/cpp/thread/packaged_task * * std::packaged_task uses C++11's rvalue references, which are not available on the * environments where MaxScale is compiled. Consequently, some care is needed * when using maxscale::packaged_task so that unintended copying does not occur. * * When C++11 is available, it should be straightforward to take the std::packed_task * into use. * * Contrary to std::packaged_task, also due to lack of functionality introduced by * C++11, maxscale::packaged_task is not fully generic, but can only package a function * returning a value and taking one argument. */ template class packaged_task { typedef typename future::internal internal; public: typedef R (*function)(T); /** * Creates a packaged_task with no task and no shared state. */ packaged_task() : m_f(NULL) , m_get_future_called(false) { } /** * Creates a packaged_task referring to the provided function. * * @param f The function to package. */ packaged_task(function f) : m_f(f) , m_sInternal(new internal) , m_get_future_called(false) { } /** * Move constructor * * @note This looks like a regular copy-constructor, but should be treated as * a move constructor. * * @param other The packaged_task to move. After the call, @c other will not * refer to a packaged task. */ packaged_task(const packaged_task& other) : m_f(other.m_f) , m_sInternal(other.m_sInternal) , m_get_future_called(other.m_get_future_called) { other.m_f = NULL; other.m_sInternal.reset(); other.m_get_future_called = false; } /** * Move a packaged_task * * @note This looks like a regular assignment operator, but the assigned value * is moved. * * @param rhs The packaged_task to move. After the call, @c rhs will not * refer to a packaged task. */ packaged_task& operator = (const packaged_task& rhs) { packaged_task copy(rhs); copy.swap(*this); return *this; } /** * Destructor */ ~packaged_task() { if (m_sInternal.get()) { ss_dassert(m_get_future_called); // The ownership of m_pFuture has moved to the future // that was obtained in the call to get_future(). if (!m_get_future_called) { MXS_ERROR("Packaged task destructed without future result having been asked for."); } } } /** * Swap the content * * @param rhs The packaged_task to swap the contents with. */ void swap(packaged_task& rhs) { std::swap(m_f, rhs.m_f); std::swap(m_sInternal, rhs.m_sInternal); std::swap(m_get_future_called, rhs.m_get_future_called); } /** * Checks the validity of the packaged_task. * * @return True, if the packaged_task contains share state, false otherwise. */ bool valid() const { return m_sInternal.get() != NULL; } /** * Returns a future which shares the same shared state as this packaged_task. * * @note @c get_future can be called only once for each packaged_task. * * @return A future. */ future get_future() { ss_dassert(!m_get_future_called); if (!m_get_future_called) { m_get_future_called = true; return future(m_sInternal); } else { MXS_ERROR("get_future called more than once."); return future(); } }; /** * Calls the stored task with the provided argument. * * After this call, anyone waiting for the shared result will be unblocked. */ void operator()(T arg) { m_sInternal->set(m_f(arg)); } private: mutable function m_f; mutable std::shared_ptr m_sInternal; mutable bool m_get_future_called; }; }