Using boost fiber at the latest version available with boost for x86_64 (Linux) via miniforge3, compiled with GCC via miniforge3, I've defined a class in a file skein.hpp
like follows:
#include <memory>
#include <boost/fiber/future/async.hpp>
#include <boost/fiber/future/future.hpp>
template <typename T> class skein {
public:
using value_type = T;
using ptr_type = std::shared_ptr<T>;
virtual ~skein() = default;
skein()
: m_future(boost::fibers::async(
std::bind(&skein<value_type>::func_p, this))) {};
skein(const skein &) = delete;
skein(skein &&) = delete;
void wait() { m_future.wait(); };
ptr_type get_ptr() {
wait();
return m_future.get();
};
value_type *get() { return get_ptr().get(); }
boost::fibers::future<ptr_type> m_future;
protected:
virtual value_type func() = 0;
ptr_type func_p() { return std::make_shared<T>(func()); }
};
I'm using this with a main.cpp
defined like as follows:
#include <iostream>
#include <skein.hpp>
class echo_int : public skein<int> {
public:
echo_int(int value) : skein(), m_value(value) {};
protected:
const int m_value;
virtual int func() { return m_value; };
};
auto main() -> int {
auto sk = new echo_int(5);
// this should work
auto ivv = sk->m_future.get();
std::cout << "Value: ";
std::cout << *ivv;
std::cout << std::endl;
auto vv = sk->get_ptr(); // this is where it fails
if (*vv.get() == 5) {
std::cout << "OK: ";
std::cout << sk->get();
std::cout << std::endl;
return 0;
} else {
std::cerr << "Unexpected Value: ";
std::cerr << *vv.get();
std::cerr << std::endl;
return 1;
}
return 0;
};
The call denoted this is where it fails
appears to be failing in where it calls into boost::fibers::detail::future_base<std::shared_ptr<int> >::wait
. The backtrace with gdb is as follows:
GNU gdb (GDB; SUSE Linux Enterprise 15) 13.2
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
[sic]
Reading symbols from ./build/dev/tests/skein_test...
(gdb) run
Starting program: /home/myuser/the_project/build/dev/tests/skein_test
Missing separate debuginfos, use: zypper install glibc-debuginfo-2.38-150600.12.1.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Value: 5
terminate called after throwing an instance of 'boost::fibers::future_uninitialized'
what(): Operation not permitted on an object without an associated state.
Program received signal SIGABRT, Aborted.
0x00007ffff7aa94ac in __pthread_kill_implementation () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff7aa94ac in __pthread_kill_implementation () from /lib64/libc.so.6
#1 0x00007ffff7a578c2 in raise () from /lib64/libc.so.6
#2 0x00007ffff7a3f64f in abort () from /lib64/libc.so.6
#3 0x00007ffff7e770d9 in __gnu_cxx::__verbose_terminate_handler () at ../../../../libstdc++-v3/libsupc++/vterminate.cc:95
#4 0x00007ffff7e756bb in __cxxabiv1::__terminate (handler=<optimized out>) at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:48
#5 0x00007ffff7e6f0e3 in std::terminate () at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:58
#6 0x00007ffff7e758be in __cxxabiv1::__cxa_throw (obj=<optimized out>,
tinfo=0x555555567af8 <typeinfo for boost::fibers::future_uninitialized>,
dest=0x55555555a7e8 <boost::fibers::future_uninitialized::~future_uninitialized()>)
at ../../../../libstdc++-v3/libsupc++/eh_throw.cc:98
#7 0x000055555555d37f in boost::fibers::detail::future_base<std::shared_ptr<int> >::wait (this=0x55555557b518)
at /opt/miniforge3/envs/cppxpy/include/boost/fiber/future/future.hpp:77
#8 0x000055555555c780 in cppxpy::skein<int>::wait (this=0x55555557b510)
at /home/myuser/the_project/source/shell/skein/skein_bak.hpp:37
#9 0x000055555555b8b1 in cppxpy::skein<int>::get_ptr (this=0x55555557b510)
at /home/myuser/the_project/source/shell/skein/skein_bak.hpp:41
#10 0x000055555555859a in main () at /home/myuser/the_project/tests/skein_test.cpp:36
(gdb)
Taking a look at the source code provided with boost fiber future.hpp
, it appears that the future's _state
pointer is a null pointer where the call is failing.
The initial call to future.get()
is not failing, however. The output from the program is as follows:
Value: 5
terminate called after throwing an instance of 'boost::fibers::future_uninitialized'
what(): Operation not permitted on an object without an associated state.
This may appear to indicate that the top-level call to future.get()
succeeds, while future.wait()
as called under the member function is failing (on whichever future it's actually accessing there).
My admittedly trivial test case was inspired after the very succinct future.cpp
example from boost fiber. I was hoping that my Python-future-esque skein class could be adapted for concurrency within a single thread, perhaps somehow resembling Python's Gevent.
I don't understand why the initial "Value: "
printout works, but the later call to future.get() is failing like so. Would there be a copy being created for the future, somewhere in my example code?
For at least the value bucket, I'd tried to avoid copying by using a smart pointer there. If there may be a copy being created for the future itself, I wonder how might one avoid this copying, if so?
Alternately, could there be anything introduced through the use of std::bind
with a member function of a virtual class, for the call to boost::fibers::async
? I was trying to follow the example code here, but to use a member function in lieu of a lambda. (Perhaps unrelated, if declaring all of the regular member functions as virtual
, this does not appear to have any effect on the error.)
The initial ""Value: "
printout works somehow, after calling future.get()
on its future. However, the call to future.wait()
fails. It seems that the future that the second call is accessing is uninitialized for purpose of wait()
?
Update: After taking another look at the boost source code, could future.get()
be anything like a single-use function here? Perhpas the future's internal _state
is being reset to a null pointer, at the initial call to get()
.
As discussed in the comments, unlike python, C++ futures are one-time use, and cannot be shared, calling get
on it consumes it and moves the contained object out of it.
there is shared_future that only returns a const reference to the underlying object and can be shared by many threads and get
can be called many times.
you can also build your own event-like future using a mutex and a condition variable and an std::optional.
note that for threads there is atomic_bool that has wait
and notify_all
that can replace the mutex and the condition variable, but it doesn't work with fibers.