First complete Solver release

Change-Id: Icd5a77c6400a57db93bdd65e61a022af95f8bacb
This commit is contained in:
Geir Horn 2024-04-18 11:15:06 +02:00
parent d2324e42a3
commit 58b37118fb
5 changed files with 230 additions and 187 deletions

View File

@ -174,7 +174,11 @@ void AMPLSolver::DefineProblem(const Solver::OptimisationProblem & TheProblem,
// The default values for the data will be loaded from the data file. This
// operation is the same as the one done for data messages, and to avoid
// code duplication the handler is just invoked using the address of this
// solver Actor as the the sender is not important for this update.
// solver Actor as the the sender is not important for this update. However,
// if the information is missing from the message, no data file should be
// loaded. It is necessary to convert the content to a string since the
// JSON library only sees the string and not its length before it has been
// unwrapped.
if( TheProblem.contains( DataFileMessage::Keys::DataFile ) &&
TheProblem.contains( DataFileMessage::Keys::NewData ) )
@ -255,7 +259,8 @@ void AMPLSolver::SolveProblem(
// supported as values.
for( const auto & [ TheName, MetricValue ] :
Solver::MetricValueType( TheContext.at( Solver::ExecutionContext ) ) )
Solver::MetricValueType( TheContext.at(
Solver::ApplicationExecutionContext::Keys::ExecutionContext ) ) )
SetAMPLParameter( TheName, MetricValue );
// Setting the given objective as the active objective and all other
@ -264,8 +269,10 @@ void AMPLSolver::SolveProblem(
std::string OptimisationGoal;
if( TheContext.contains( Solver::ObjectiveFunctionLabel ) )
OptimisationGoal = TheContext.at( Solver::ObjectiveFunctionLabel );
if( TheContext.contains(
Solver::ApplicationExecutionContext::Keys::ObjectiveFunctionLabel ) )
OptimisationGoal = TheContext.at(
Solver::ApplicationExecutionContext::Keys::ObjectiveFunctionLabel );
else if( !DefaultObjectiveFunction.empty() )
OptimisationGoal = DefaultObjectiveFunction;
else
@ -337,7 +344,8 @@ void AMPLSolver::SolveProblem(
// application execution context has the deployment flag set.
Solver::Solution::VariableValuesType VariableValues;
bool DeploymentFlagSet = TheContext.at( DeploymentFlag ).get<bool>();
bool DeploymentFlagSet
= TheContext.at( Solver::Solution::Keys::DeploymentFlag ).get<bool>();
for( auto Variable : ProblemDefinition.getVariables() )
{
@ -351,7 +359,8 @@ void AMPLSolver::SolveProblem(
// The found solution can then be returned to the requesting actor or topic
Send( Solver::Solution(
TheContext.at( Solver::TimeStamp ).get< Solver::TimePointType >(),
TheContext.at(
Solver::Solution::Keys::TimeStamp ).get< Solver::TimePointType >(),
OptimisationGoal, ObjectiveValues, VariableValues,
DeploymentFlagSet
), TheRequester );

View File

@ -40,7 +40,7 @@ namespace NebulOuS
void MetricUpdater::AddMetricSubscription(
const MetricTopic & MetricDefinitions, const Address OptimiserController )
{
JSON TheMetrics = MetricDefinitions.at( MetricList );
JSON TheMetrics = MetricDefinitions.at( MetricTopic::Keys::MetricList );
if( TheMetrics.is_array() )
{
@ -59,16 +59,12 @@ void MetricUpdater::AddMetricSubscription(
TheMetricNames.insert( MetricRecordPointer->first );
// If a new metric was added, a subscription will be set up for this
// new metric, and the flag indicating that values have been received
// for all metrics will be reset since this new metric has yet to receive
// its first value
if( MetricAdded )
{
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
std::string( MetricValueRootString ) + MetricRecordPointer->first ),
std::string( MetricValueUpdate::MetricValueRootString )
+ MetricRecordPointer->first ),
GetSessionLayerAddress() );
AllMetricValuesSet = false;
@ -85,7 +81,7 @@ void MetricUpdater::AddMetricSubscription(
{
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
std::string( MetricValueRootString ) + TheMetric ),
std::string( MetricValueUpdate::MetricValueRootString ) + TheMetric ),
GetSessionLayerAddress() );
MetricValues.erase( TheMetric );
@ -138,14 +134,16 @@ void MetricUpdater::UpdateMetricValue(
{
Theron::AMQ::TopicName TheTopic
= TheMetricTopic.AsString().erase( 0,
NebulOuS::MetricValueRootString.size() );
MetricValueUpdate::MetricValueRootString.size() );
if( MetricValues.contains( TheTopic ) )
{
MetricValues.at( TheTopic ) = TheMetricValue.at( NebulOuS::ValueLabel );
MetricValues.at( TheTopic )
= TheMetricValue.at( MetricValueUpdate::Keys::ValueLabel );
ValidityTime = std::max( ValidityTime,
TheMetricValue.at( NebulOuS::TimePoint ).get< Solver::TimePointType >() );
TheMetricValue.at(
MetricValueUpdate::Keys::TimePoint ).get< Solver::TimePointType >() );
}
}
@ -183,7 +181,8 @@ void MetricUpdater::SLOViolationHandler(
[](const auto & MetricValue){ return MetricValue.is_null(); } ))) )
{
Send( Solver::ApplicationExecutionContext(
SeverityMessage.at( NebulOuS::TimePoint ).get< Solver::TimePointType >(),
SeverityMessage.at(
MetricValueUpdate::Keys::TimePoint ).get< Solver::TimePointType >(),
MetricValues, true
), TheSolverManager );
@ -250,17 +249,17 @@ MetricUpdater::MetricUpdater( const std::string UpdaterName,
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
NebulOuS::MetricSubscriptions ),
MetricTopic::AMQTopic ),
GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
NebulOuS::ReconfigurationTopic ),
ReconfigurationMessage::AMQTopic ),
GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
NebulOuS::SLOViolationTopic ),
SLOViolation::AMQTopic ),
GetSessionLayerAddress() );
}
@ -275,24 +274,25 @@ MetricUpdater::~MetricUpdater()
{
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
NebulOuS::MetricSubscriptions ),
MetricTopic::AMQTopic ),
GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
NebulOuS::ReconfigurationTopic ),
ReconfigurationMessage::AMQTopic ),
GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
NebulOuS::SLOViolationTopic ),
SLOViolation::AMQTopic ),
GetSessionLayerAddress() );
std::ranges::for_each( std::views::keys( MetricValues ),
[this]( const Theron::AMQ::TopicName & TheMetricTopic ){
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
std::string( MetricValueRootString ) + TheMetricTopic ),
std::string( MetricValueUpdate::MetricValueRootString )
+ TheMetricTopic ),
GetSessionLayerAddress() );
});
}

View File

@ -68,74 +68,6 @@ using JSON = nlohmann::json; // Short form name space
namespace NebulOuS
{
/*==============================================================================
Basic interface definitions
==============================================================================*/
//
// Definitions for the terminology to facilitate changing the lables of the
// various message labels without changing the code. The definitions are
// compile time constants and as such should not lead to any run-time overhead.
// The JSON attribute names may be found under the "Predicted monitoring
// metrics" section on the Wiki page [1].
constexpr std::string_view ValueLabel = "metricValue";
constexpr std::string_view TimePoint = "predictionTime";
// The topic used for receiving the message(s) defining the metrics of the
// application execution context as published by the Optimiser Controller is
// defined next.
constexpr std::string_view MetricSubscriptions
= "eu.nebulouscloud.optimiser.controller.metric_list";
// The JSON message attribute for the list of metrics is another JSON object
// stored under the following key, see the Event type III defined in
// https://158.39.75.54/projects/nebulous-collaboration-hub/wiki/slo-severity-based-violation-detector
// where the name of the metric is defined under as sub-key.
constexpr std::string_view MetricList = "metrics";
// The metric value messages will be published on different topics and to
// check if an inbound message is from a metric value topic, it is necessary
// to test against the base string for the metric value topics according to
// the Wiki-page [1]
constexpr std::string_view MetricValueRootString
= "eu.nebulouscloud.monitoring.predicted.";
// The SLO violation detector will publish a message when a reconfiguration is
// deamed necessary for a future time point called "Event type V" on the wiki
// page [3]. The event contains a probability for at least one of the SLOs
// being violated at the predicted time point. It is not clear if the assessment
// is being made by the SLO violation detector at every new metric prediction,
// or if this event is only signalled when the probability is above some
// internal threshold of the SLO violation detector.
//
// The current implementation assumes that the latter is the case, and hence
// just receiving the message indicates that a new application configuration
// should be found given the application execution context as predicted by the
// metric values recorded by the Metric Updater. Should this assumption be
// wrong, the probability must be compared with some user set threshold for
// each message, and to cater for this the probability field will always be
// compared to a threshold, currently set to zero to ensure that every event
// message will trigger a reconfiguration.
//
// The messages from the Optimizer Controller will be sent on a topic that
// should follow some standard topic convention.
constexpr std::string_view SLOViolationTopic
= "eu.nebulouscloud.monitoring.slo.severity_value";
// When a reconfiguration has been enacted by the Optimiser Controller and
// a new configuration is confirmed to be running on the new platofrm, it will
// send a message to inform all other components that the reconfiguration
// has happened on the following topic.
constexpr std::string_view ReconfigurationTopic
= "eu.nebulouscloud.optimiser.adaptations";
/*==============================================================================
Metric Updater
@ -178,14 +110,12 @@ private:
Solver::TimePointType ValidityTime;
// There is also a flag to indicate when all metric values have received
// values since optimising for a application execution context defiend all
// metrics requires that at least one value is received for each metric. This
// condition could be tested before sending the request to find a new
// solution, but this means testing all metrics in a linear scan for a
// condition that will only happen initially until all metrics have been seen
// and so it is better for the performance if there is a flag to check for
// this condition.
// When an SLO violation message is received the current vector of metric
// values should be sent as an application execution context (message) to the
// Solution Manager actor that will invoke a solver to find the optimal
// configuration for this configuration. The Metric Updater must therefore
// know the address of the Soler Manager, and this must be passed to
// the constructor.
bool AllMetricValuesSet;
@ -204,18 +134,35 @@ private:
{
public:
// The topic used for receiving the message(s) defining the metrics of the
// application execution context as published by the Optimiser Controller
// is the following
static constexpr std::string_view AMQTopic
= "eu.nebulouscloud.optimiser.controller.metric_list";
// The EXN middleware sending the AMQP messages for the other component
// of the NebulOuS project only sends JSON objects, meaning that the list
// of metric names to subscribe is sent as a JSON array, but it must be
// embedded in a map with a single key, see the message format described
// in the Wiki page at
// https://openproject.nebulouscloud.eu/projects/nebulous-collaboration-hub/wiki/1-optimiser-controller#controller-to-metric-updater-and-ems-metric-list
struct Keys
{
static constexpr std::string_view MetricList = "metrics";
};
// Constructors
MetricTopic( void )
: JSONTopicMessage( std::string( MetricSubscriptions ) )
: JSONTopicMessage( AMQTopic )
{}
MetricTopic( const MetricTopic & Other )
: JSONTopicMessage( Other )
{}
// MetricTopic( const JSONTopicMessage & Other )
// : JSONTopicMessage( Other )
// {}
virtual ~MetricTopic() = default;
};
@ -241,6 +188,25 @@ private:
{
public:
// The metric value messages will be published on different topics and to
// check if an inbound message is from a metric value topic, it is
// necessary to test against the base string for the metric value topics
// according to the Wiki-page describing the Type II message:
// https://openproject.nebulouscloud.eu/projects/nebulous-collaboration-hub/wiki/monitoringdata-interface#type-ii-messages-predicted-monitoring-metrics
static constexpr std::string_view MetricValueRootString
= "eu.nebulouscloud.monitoring.predicted.";
// Only two of the fields in this message will be looked up and stored
// in the current application context map.
struct Keys
{
static constexpr std::string_view
ValueLabel = "metricValue",
TimePoint = "predictionTime";
};
MetricValueUpdate( void )
: JSONWildcardMessage( std::string( MetricValueRootString ) )
{}
@ -263,21 +229,48 @@ private:
// SLO violations
// --------------------------------------------------------------------------
//
// The SLO Violation detector publishes an event to indicate that at least
// one of the constraints for the application deployment will be violated in
// the predicted future, and that the search for a new solution should start.
// This will trigger the the publication of the Solver's Application Execution
// context message. The context message will contain the current status of the
// metric values, and trigger a solver to find a new, optimal variable
// assignment to be deployed to resolve the identified problem.
// The SLO violation detector will publish a message when a reconfiguration is
// deamed necessary for a future time point called "Event type VI" on the wiki
// page: https://openproject.nebulouscloud.eu/projects/nebulous-collaboration-hub/wiki/slo-severity-based-violation-detector#output-event-type-vi
// The event contains a probability for at least one of the SLOs being
// violated at the predicted time point. It is not clear if the assessment
// is being made by the SLO violation detector at every new metric prediction,
// or if this event is only signalled when the probability is above some
// internal threshold of the SLO violation detector.
//
// The current implementation assumes that the latter is the case, and hence
// just receiving the message indicates that a new application configuration
// should be found given the application execution context as predicted by the
// metric values recorded by the Metric Updater. Should this assumption be
// wrong, the probability must be compared with some user set threshold for
// each message, and to cater for this the probability field will always be
// compared to a threshold, currently set to zero to ensure that every event
// message will trigger a reconfiguration.
class SLOViolation
: public Theron::AMQ::JSONTopicMessage
{
public:
// The messages from the SLO Violation Detector will be sent on a topic that
// should follow some standard topic convention.
static constexpr std::string_view AMQTopic
= "eu.nebulouscloud.monitoring.slo.severity_value";
// The only information taken from this detction message is the prediction
// time which will be used as the time for the application's execution
// context when this is forwarded to the solvers for processing.
struct Keys
{
static constexpr std::string_view TimePoint = "predictionTime";
};
// Constructors
SLOViolation( void )
: JSONTopicMessage( std::string( SLOViolationTopic ) )
: JSONTopicMessage( AMQTopic )
{}
SLOViolation( const SLOViolation & Other )
@ -311,18 +304,28 @@ private:
bool ReconfigurationInProgress;
// When the reconfiguration has been done and the Optimizer Controller
// confirms that the application is running in a new configuration, it will
// send a reconfiguration completed message. This message will just be a
// JSON message.
// When a reconfiguration has been enacted by the Optimiser Controller and
// a new configuration is confirmed to be running on the new platofrm, it
// will send a message to inform all other components that the
// reconfiguration has happened. The event is just the reception of the
// message and its content will not be processed, so there are no keys for
// the JSON map received.
class ReconfigurationMessage
: public Theron::AMQ::JSONTopicMessage
{
public:
// The topic for the reconfiguration finished messages is defined by the
// optimiser as the sender.
static constexpr std::string_view AMQTopic
= "eu.nebulouscloud.optimiser.controller.reconfiguration";
// Constructors
ReconfigurationMessage( void )
: JSONTopicMessage( std::string( ReconfigurationTopic ) )
: JSONTopicMessage( AMQTopic )
{}
ReconfigurationMessage( const ReconfigurationMessage & Other )

View File

@ -79,52 +79,6 @@ public:
// Application Execution Context
// --------------------------------------------------------------------------
//
// The message is defined as a JSON message representing an attribute-value
// object. The attributes expected are defined as constant strings so that
// the actual textual representation can be changed without changing the code
//
// "Timestamp" : This is the field giving the implicit order of the
// different application execution execution contexts waiting for being
// solved when there are more requests than there are solvers available to
// work on the different problems.
static constexpr std::string_view TimeStamp = "Timestamp";
// There is also a definition for the objective function label since a multi-
// objective optimisation problem can have multiple objective functions and
// the solution is found for only one of these functions at the time even
// though all objective function values will be returned with the solution,
// the solution will maximise only the objective function whose label is
// given in the application execution context request message.
//
// The Application Execution Cntext message may contain the name of the
// objective function to maximise. If so, this should be stored under the
// key name indicated here. However, if the objective function name is not
// given, the default objective function is used. The default objective
// function will be named when defining the optimisation problem.
static constexpr std::string_view
ObjectiveFunctionLabel = "ObjectiveFunction";
// Finally, there is another JSON object that defines all the metric name and
// value pairs that define the actual execution context. Note that there must
// be at least one metric-value pair for the request to be valid.
static constexpr std::string_view ExecutionContext = "ExecutionContext";
// Finally, the execution context can come from the Metric Collector actor
// as a consequence of an SLO Violation being detected. In this case the
// optimised solution found by the solver should trigger a reconfiguration.
// However, various application execution context can also be tried for
// simulating future events and to investigate which configuration would be
// the best for these situations. In this case the optimised solution should
// not reconfigure the running application. For this reason there is a flag
// in the message indicating whether the solution should be deployed, and
// its default value is 'false' to prevent solutions form accidentially being
// deployed.
static constexpr std::string_view DeploymentFlag = "DeploySolution";
// To ensure that the execution context is correctly provided by the senders
// The expected metric value structure is defined as a type based on the
// standard unsorted map based on a JSON value object since this can hold
@ -161,6 +115,48 @@ public:
static constexpr std::string_view AMQTopic
= "eu.nebulouscloud.optimiser.solver.context";
// The keys used in the JSON message to send are defined first:
//
// "Timestamp" : This is the field giving the implicit order of the
// different application execution execution contexts waiting for being
// solved when there are more requests than there are solvers available
// to work on the different problems.
// "ObjectFunction" : There is also a definition for the objective function
// label since a multi-objective optimisation problem can have multiple
// objective functions and the solution is found for only one of these
// functions at the time even though all objective function values will
// be returned with the solution, the solution will maximise only the
// objective function whose label is given in the application execution
// context request message. The Application Execution Cntext message may
// contain the name of the objective function to maximise. If so, this
// should be stored under the key name indicated here. However, if the
// objective function name is not given, the default objective function
// is used. The default objective function will be named when defining
// the optimisation problem.
// "ExecutionContext" : Defines all the metric name and value pairs that
// define the actual execution context. Note that there must be at least
// one metric-value pair for the request to be valid.
// "DeploySolution" : The execution context can come from the Metric
// Collector actor as a consequence of an SLO Violation being detected.
// In this case the optimised solution found by the solver should trigger
// a reconfiguration. However, various application execution context can
// also be tried for simulating future events and to investigate which
// configuration would be the best for these situations. In this case the
// optimised solution should not reconfigure the running application. For
// this reason there is a flag in the message indicating whether the
// solution should be deployed, and its default value is 'false' to
// prevent solutions form accidentially being deployed.
struct Keys
{
static constexpr std::string_view
TimeStamp = "Timestamp",
ObjectiveFunctionLabel = "ObjectiveFunction",
ExecutionContext = "ExecutionContext",
DeploymentFlag = "DeploySolution";
};
// The full constructor takes the time point, the objective function to
// solve for, and the application's execution context as the metric map
@ -169,10 +165,10 @@ public:
const MetricValueType & TheContext,
bool DeploySolution = false )
: JSONTopicMessage( std::string( AMQTopic ),
{ { std::string( TimeStamp ), MicroSecondTimePoint },
{ std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID },
{ std::string( ExecutionContext ), TheContext },
{ std::string( DeploymentFlag ), DeploySolution }
{ { Keys::TimeStamp, MicroSecondTimePoint },
{ Keys::ObjectiveFunctionLabel, ObjectiveFunctionID },
{ Keys::ExecutionContext, TheContext },
{ Keys::DeploymentFlag, DeploySolution }
}) {}
// The constructor omitting the objective function identifier is similar
@ -183,9 +179,9 @@ public:
const MetricValueType & TheContext,
bool DeploySolution = false )
: JSONTopicMessage( std::string( AMQTopic ),
{ { std::string( TimeStamp ), MicroSecondTimePoint },
{ std::string( ExecutionContext ), TheContext },
{ std::string( DeploymentFlag ), DeploySolution }
{ { Keys::TimeStamp, MicroSecondTimePoint },
{ Keys::ExecutionContext, TheContext },
{ Keys::DeploymentFlag, DeploySolution }
}) {}
// The copy constructor simply passes the job on to the JSON Topic
@ -206,9 +202,9 @@ public:
virtual ~ApplicationExecutionContext() = default;
};
// The handler for this message is virtual as it where the real action will
// happen and the search for the optimal solution will hopefully lead to a
// feasible soltuion that can be returned to the sender of the applicaton
// The handler for this message is virtual as it is where the real action
// will happen and the search for the optimal solution will hopefully lead
// to a feasible soltuion that can be returned to the sender of the applicaton
// context.
protected:
@ -241,26 +237,47 @@ public:
{
public:
// There are some aliases that can be used in other Actors processing this
// message in order to ensure portability
using ObjectiveValuesType = MetricValueType;
using VariableValuesType = MetricValueType;
static constexpr std::string_view ObjectiveValues = "ObjectiveValues";
static constexpr std::string_view VariableValues = "VariableValues";
// The topic for which the message is published is defined first
static constexpr std::string_view AMQTopic
= "eu.nebulouscloud.optimiser.solver.solution";
// Most of the message keys are the same as for the application execution
// context, but there are two new:
//
// "ObjectiveValues" : This holds a map of objective function names and
// their values under the currently found solution which is optimised
// for the given objective function or the default objective function.
// The other objective values is useful if one is searching for the
// Pareto front of the problem.
// "VariableValues" : This key is a map holding the variable names and
// their values found by the solver for the optimal solution. This is
// used to reconfigure the application.
struct Keys : public ApplicationExecutionContext::Keys
{
static constexpr std::string_view
ObjectiveValues = "ObjectiveValues",
VariableValues = "VariableValues";
};
Solution( const TimePointType MicroSecondTimePoint,
const std::string ObjectiveFunctionID,
const ObjectiveValuesType & TheObjectiveValues,
const VariableValuesType & TheVariables,
bool DeploySolution )
: JSONTopicMessage( std::string( AMQTopic ) ,
{ { std::string( TimeStamp ), MicroSecondTimePoint },
{ std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID },
{ std::string( ObjectiveValues ) , TheObjectiveValues },
{ std::string( VariableValues ), TheVariables },
{ std::string( DeploymentFlag ), DeploySolution }
{ { Keys::TimeStamp, MicroSecondTimePoint },
{ Keys::ObjectiveFunctionLabel, ObjectiveFunctionID },
{ Keys::ObjectiveValues, TheObjectiveValues },
{ Keys::VariableValues, TheVariables },
{ Keys::DeploymentFlag, DeploySolution }
} )
{}
@ -335,6 +352,11 @@ public:
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
OptimisationProblem::AMQTopic
), GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
ApplicationExecutionContext::AMQTopic
), GetSessionLayerAddress() );
}
Solver() = delete;
@ -342,10 +364,18 @@ public:
virtual ~Solver()
{
if( HasNetwork() )
{
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
OptimisationProblem::AMQTopic
), GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
ApplicationExecutionContext::AMQTopic
), GetSessionLayerAddress() );
}
}
};

View File

@ -174,7 +174,8 @@ private:
const Address TheRequester )
{
ContextQueue.emplace(
TheContext.at( Solver::TimeStamp ).get< Solver::TimePointType >(),
TheContext.at( Solver::ApplicationExecutionContext::Keys::TimeStamp
).get< Solver::TimePointType >(),
TheContext );
DispatchToSolvers();