5845f1a41d
This symbol is defined as a static member object inside of a boost detail header. When boost headers are used in a project that uses Boost in both the main binary as well as dlopen()'d shlibs, the top_ symbol gets duplicated and the metadata gets partitioned. We use the Boost shlib to unify both the main binary and the shlibs to use the same memory address for top_. This involves marking the templated object call_stack::top_ as "extern" and then declaring to Boost that we intend to use the shlibs.
103 lines
2.8 KiB
C++
103 lines
2.8 KiB
C++
#include <boostAsioLinkageFix.h>
|
|
#include <iostream>
|
|
#include <config.h>
|
|
#include <componentThread.h>
|
|
#include <boost/asio/io_service.hpp>
|
|
#include <boost/asio/deadline_timer.hpp>
|
|
#include <boost/system/error_code.hpp>
|
|
#include <spinLock.h>
|
|
#include <asynchronousBridge.h>
|
|
#include <user/stimulusBuffer.h>
|
|
|
|
namespace smo {
|
|
namespace stim_buff {
|
|
|
|
void StimulusBuffer::stop()
|
|
{
|
|
shouldContinue.store(false);
|
|
|
|
// Set up a timeout bridge using the io_service
|
|
boost::asio::deadline_timer delayTimer(ioService);
|
|
AsynchronousBridge bridge(ioService);
|
|
|
|
// Set up the delay to let in-flight operation finish
|
|
delayTimer.expires_from_now(
|
|
boost::posix_time::milliseconds(getStopDelayMs()));
|
|
|
|
delayTimer.async_wait(
|
|
[&bridge](const boost::system::error_code& error)
|
|
{
|
|
(void)error;
|
|
|
|
// Always signal complete, whether timeout expired or was cancelled
|
|
bridge.setAsyncOperationComplete();
|
|
});
|
|
|
|
bridge.waitForAsyncOperationCompleteOrIoServiceStopped();
|
|
|
|
std::cout << __func__ << ": Stopped stimulus buffer for device "
|
|
<< deviceAttachmentSpec.deviceSelector << std::endl;
|
|
|
|
// After delay, cancel timer and perform cleanup
|
|
timer.cancel();
|
|
}
|
|
|
|
void StimulusBuffer::scheduleNextTimeout(int delayMs)
|
|
{
|
|
if (!shouldContinue.load())
|
|
{ return; }
|
|
|
|
// Schedule the next timeout using the provided delay
|
|
timer.expires_from_now(
|
|
boost::posix_time::milliseconds(delayMs));
|
|
|
|
timer.async_wait(
|
|
std::bind(
|
|
&StimulusBuffer::onTimeout, this, std::placeholders::_1));
|
|
}
|
|
|
|
void StimulusBuffer::onTimeout(const boost::system::error_code& error)
|
|
{
|
|
// Timer was cancelled, which is expected when stopping
|
|
if (error == boost::asio::error::operation_aborted) {
|
|
return;
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
std::cerr << "StimulusBuffer: Timer error: " << error.message()
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
|
|
if (!shouldContinue.load())
|
|
{ return; }
|
|
|
|
/** EXPLANATION:
|
|
* We need to ensure that there's only ever one stimframe being produced
|
|
* during any CONFIG_STIMBUFF_FRAME_PERIOD_MS period. To guarantee this, we
|
|
* use a spinlock.
|
|
*
|
|
* When a new frame is to be produced, the async producer will first acquire
|
|
* the frameAssemblyLimiter spinlock. This way, when the next timeout is
|
|
* fired it can check whether its predecessor stimframe has finished being
|
|
* produced. If the preceding stimframe is still being produced, then we'll
|
|
* sleep for CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS ms before trying again.
|
|
*/
|
|
int nextWakeupDelayMs;
|
|
if (frameAssemblyRateLimiter.tryAcquire())
|
|
{ nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS; }
|
|
else
|
|
{ nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS; }
|
|
|
|
// Call the derived class's frame production handler
|
|
stimFrameProductionTimesliceInd();
|
|
// Note: The lock should be released when frame production completes
|
|
|
|
// Schedule next timeout with the pre-determined duration
|
|
scheduleNextTimeout(nextWakeupDelayMs);
|
|
}
|
|
|
|
} // namespace stim_buff
|
|
} // namespace smo
|