This performs a more complete device initialization and attachment
sequence. We'll do the corresponding teardown in the shutdown
sequence later.
We might probably do it as deviceRoleGoneAwayInd()
We've decided to add a separate notion of a DeviceRole to track attached
device roles now. We no longer use the collection of deviceSpecs to
track which roles have been attached. Rather, this list will simply
collate all known deviceAttachment specs which are expected to be
maintained in an attached state.
SMO can periodically scan through these and cross-reference this
collection with the collection of attachedDeviceRoles. Then it can
re-try to attach those which aren't currently attached at any given
moment. This will give resilience against device attachment failures
or device resets/malfunctions, at runtime.
This is logically cleaner and it begins preparing our next set
of restructuring changes. To wit: we're revamping the device
manager to distinguish between devices and their roles.
We added the code to trace all the contins linked to a particular
Lockvoker, into SerializedAsyncContinuation. This basically
ensures that we'll almost never deal with a deadlock. So cool.
We added a timestamp to each Lockvoker so that we can detect when
a lockvoker has been in a qutex for "too long", where "too long"
is defined arbitrarily as 500ms.
Next we're going to change the way we create callbacks to enable
us to more explicitly access the sh_ptr<AsyncContin> via
the callback object.
We now detect that a deadlock is likely when
CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS has elapsed. This is the
preliminary work required to do a backtrace through the call
stack and figure out if a deadlock has really occured.
To do this, we'd have to go through the async call chain and
search for a previous caller which acquired the same qutex as
the one that first failed during this Lockvoker LockSet acquisition
attempt.
Implements: LockSet, SerializedAsynchronousContinuation,
LockerAndInvoker, LockerAndInvokerBase, Qutex.
Very big leap in functionality here. See qutexes.md for
an explanation of what we've done.
Async: Use new [Non]PostedAsyncCont and callOriginalCb
This new hierarchy of classes gives us a central mechanism for
managing both reply-posting and lockSpec unlocking.
* callOriginalCb: Now uses a modern C++ variadic template design
enabling it to handle both direct calling and std::bind()
re-binding of an arbitrary number of arguments from the caller.
This enables us to mostly eliminate the repeated, bespoke
definitions of callOriginalCb littered throughout the codebase.
We've also propagated these changes throughout the codebase in
this patch.
They are posted to Marionette.
* We also fixed callOriginCb invocations;
* Also made posted CBs use std::bind instead of greedily
early-invoking the CB on the servicing thread's stack.
We now have mind::initialize/finalizeReq post their requests
to Mrntt instead of executing on the caller's thread context.
We also fixed the way that we invoke callbacks by properly wrapping
it in a std::bind.
We no longer need them because we now have
mrntt::mrntt.finalizeReq(), which does a more holistic job of
shutting down Marionette (and thus, ultimately, Salmanoff).
If even one step in Body.initializeReq was executed at all, then
whether or not it succeeded, we consider the body component to have
been initialized, at least with respect to whether finalizeReq
ought to run.
We now run body.finalizeReq even if body.init wasn't called. We'll
do a finer-grained check on each aspect of Body that needs to be
finalized now. This check was too large-grained.
This leverages the new clean dynamic allocation of the globalMind
object to make the mrntt::main and SMO's initialization and
shutdown much cleaner. We no longer concern ourselves with
shutting down the Mind threads inside of mrntt::main, but rather
we leave that state machine to the Mind class and Mrntt component.
This makes the initialization sequence much cleaner and conceptually
well encapsulated.
We also now dynamically allocate the Mind objects. They're allocated
dynamically by Mrntt inside of initializeReq. This means that we no
longer have to worry about jolting and cleaning up the running threads
of global mind object even when we never explicitly called
Mind.initializeReq.
Along with other conceptual improvements to our abstractions, this
patch also gets us to a real "end of program initialization" point
for the first time.
We now allocate globalMind locally inside of marionetteMain. Why?
Before now, we had an asymmetric threading situation where the
globalMind's threads were initialized at during global constructor
invocation and not on demand. This meant that we had to shut down
those threads even if we had never got to the point of calling
Mind::initializeReq.
This significantly complicated our shutdown sequence since we had
to factor in the lifetime of the std::thread objects inside of the
ComponentThreads which were inside of the globalMind object.
Now, if we hadn't called Mind::initializeReq, we don't have to
perform any Mind::finalizeReq or adjacent operations. Shutdown is
symmetrically mirrored against the operations we actually performed
during execution.
We introduced some complexity by splitting ComponentThreads into
two derivative types (MindThread and MarionetteThread) but I think
in the long term we'll be able to massage this split into a much
cleaner situation overall.
In Mrntt, we now initialize Mind:: object threads before calling
initializeSalmanoffReq().
We've also propagated the spinscale async pattern into the Mind
class.
This is the culmination of a lot of changes over the last week. We're
making SMO basically fully async in many areas, and then preparing to
implement the spinqueueing mechanism for locking.