diff --git a/docs/async-bridgding.md b/docs/async-bridgding.md new file mode 100644 index 0000000..a4e3a28 --- /dev/null +++ b/docs/async-bridgding.md @@ -0,0 +1,67 @@ +Ok: I realized I may be able to bridge async sequences without needing needing to make a bunch of functions async. The basic thing is: + +``` +funcThatCallsAsyncFuncsButWhoseSignatureIsItselfSync(ComponentThread &ct) +{ + std::atomic continuationCondition(false); + + async_call(ct, [] { + // Do stuff that will enqueue events on ct. + continuationCondition.store(true); + ComponentThread::getSelf()->getIoService() + .post([]{}); + }); + for (;;) + { + /* We, the thread actually executing this sequence + * here, may not actually be the thread that owns + * ct, in which case, ct's owner will dequeue the stuff + * that async_call() sent it to do, then conclude its + * processing. It may or may not send a message back + * to the thread executing this sync sequence here. + * + * If we are the same thread as ct, then wonderful: + * we will dequeue the messsage ourself and process + * it, then conclude the processing. We may or may + * not send a message back to ourself at the end of + * the processing but it doesn't matter since we'll + * have to check the condvar in the loop post- + * conditions before the next iteration. + * + * The problem is the first case where ct is a foreign + * thread which may not send us a wakeup message + * when condvar has been modified, and we may + * hypothetically never get another signal from any + * other thread. + * This can be solved by just ensuring that this thread + * always gives a callback to async_call() which first + * modifies condvar, and then sends an empty message + * to itself. + */ + ct.run_one(); + if (continuationCondition.load()==true + ||!ct.isRunnable()) + { + break; + } + } + + /* We've now bridged the async calls into this + * sync function's body without losing any + * event responsiveness for the main loop. + */ + // Continue executing normally... +} +``` + +This should be a complete solution for async bridging. + +Now let's try it first in the sendHandshake sequence. We'll try to use non-blocking socket api calls to send the heartbeat and wait for a response asynchronously. Don't use boost:asio:socket functions because they cause a segfault due to a bug (see /CMakelists.txt). To wait for events on the socket, setup boost to wait on the socket or bind FD. + +Async_call is OBVIOUSLY NOT a function you're expected to implement. It's merely a placeholder for any async sequence we call inside of the sync function. + +You're expected to get rid of the io_context auto-scope object inside of executeHandshake, and use the stored Device.componentThread's io_context instead. You are expected to refrain from associating Device.componentThread.io_service with any boost::asio::socket stuff. You will have to write manual posix socket api code, and use a boost::file_descriptor to catch wakeup events on the socket from the UDP events like recv-data-ready. + +You're basically going to break up executeHandshake into a series of lambdas, but use the pattern I described above to keep it as one synchronous function by bridging it. executeHandshake is the synchronous function that must remain synchronous to its caller. Internally, you must split up executeHandshake into several lambdas, and then at the end you set the condvar and post a message back to CompnentThread::getSelf()->io_service. Then, since executeHandshake() has a bridging sequence that loops and calls run_one() until the condvar is set, executeHandshake will resume executing after that loop exits. + +Makes sense?