Commit 00346326 authored by Charles Leggett's avatar Charles Leggett
Browse files

Revert "Reimplementation of DataHandle (!462)"

This reverts merge request !462
parent 9b837755
......@@ -51,8 +51,6 @@
// ============================================================================
#include "boost/format.hpp"
#include "boost/tokenizer.hpp"
#include <boost/none.hpp>
#include <boost/optional.hpp>
// ============================================================================
// Disable warning on windows
#ifdef _WIN32
......@@ -150,31 +148,44 @@ StatusCode GaudiCommon<PBASE>::
auto rootName = dataMgrSvc->rootName();
if ( !rootName.empty() && '/' != rootName.back() ) rootName += "/";
auto fixLocation = [this, rootName]( const DataObjID& id ) -> boost::optional<DataObjID> {
// Compute the updated location
const std::string& location = id.key();
auto fixLocation = [this, rootName]( const std::string& location ) -> std::string {
auto tokens = boost::tokenizer<boost::char_separator<char>>{location, boost::char_separator<char>{":"}};
auto fixedLocation =
auto result =
std::accumulate( tokens.begin(), tokens.end(), std::string{}, [&]( std::string s, const std::string& tok ) {
std::string r = fullTESLocation( tok, UseRootInTES );
// check whether we have an absolute path if yes use it - else prepend DataManager Root
if ( r[0] != '/' ) r = rootName + r;
return s.empty() ? r : s + ':' + r;
} );
if ( result != location && this->msgLevel( MSG::DEBUG ) )
this->debug() << "Changing " << location << " to " << result << endmsg;
return result;
};
// If no change occured, signal it to the caller with an empty optional
if ( fixedLocation == location ) return boost::none;
// Else compute and return an updated DataObjID
if ( this->msgLevel( MSG::DEBUG ) ) {
this->debug() << "Changing " << location << " to " << fixedLocation << endmsg;
class DHHFixer : public IDataHandleVisitor
{
public:
DHHFixer( std::function<std::string( const std::string& )> f ) : m_f( std::move( f ) ) {}
void visit( const IDataHandleHolder* idhh ) override
{
if ( !idhh ) return;
std::string r;
for ( auto h : idhh->inputHandles() ) {
r = m_f( h->objKey() );
if ( r != h->objKey() ) h->updateKey( r );
}
for ( auto h : idhh->outputHandles() ) {
r = m_f( h->objKey() );
if ( r != h->objKey() ) h->updateKey( r );
}
}
DataObjID result = id;
result.updateKey( fixedLocation );
return result;
private:
std::function<std::string( const std::string& )> m_f;
};
this->m_updateDependencies = fixLocation;
this->m_updateDataHandles = std::make_unique<DHHFixer>( fixLocation );
return sc;
}
......
......@@ -36,7 +36,6 @@ set(GaudiExamples_srcs
src/AlgTools/*.cpp
src/AnyData/*.cpp
src/DataOnDemand/*.cpp
src/DataHandles/*.cpp
src/AlgErrAud/*.cpp
src/NTuples/*.cpp
src/CounterEx/*.cpp
......
......@@ -29,7 +29,7 @@ AnyDataPutAlgorithm::AnyDataPutAlgorithm( const std::string& name, ISvcLocator*
: GaudiAlgorithm( name, pSvcLocator )
{
for ( int i = 0; i < 100; i++ ) {
m_id_vec.emplace_back( "/Event/Test/Ids" + std::to_string( i ), Gaudi::v1::DataHandle::Writer, this );
m_id_vec.emplace_back( "/Event/Test/Ids" + std::to_string( i ), Gaudi::DataHandle::Writer, this );
}
}
......
#include "GaudiKernel/Algorithm.h"
#include "GaudiKernel/DataObjID.h"
#include "GaudiKernel/EventDataHandle.h"
#include <array>
#include <stdexcept>
#include <string>
#include <utility>
// ============================================================================
/** @file
* Test harness checking that the new DataHandles declare data flow correctly
*
* The following data flow graph is being tested. It happens to be one of
* the simplest graphs that checks all useful combinations of 0, 1, and 2
* inputs and outputs (ie anything but no inputs and no outputs).
*
* In the schematic below, named boxes are algorithms and numbers are the
* values that get passed around between them, which double as graph edge
* identifiers. This simplifies implementation, at the cost of breaking
* reconfiguration of the data flow graph from the Python test script. For
* testing code, I think this is a fine trade-off.
*
* +---+ 1 +---+ 2 +---+ 3 +---+
* | A |-->| |--------->| |-->| C | A: 0 inputs and 1 output
* +---+ | | | | +---+ B: 0 inputs and 2 outputs
* | H | | E | C: 1 input and 0 outputs
* +---+ 4 | | 5 +---+ | | 6 +---+ D: 1 input and 1 output
* | |-->| |-->| | | |-->| | E: 1 input and 2 outputs
* | | +---+ | | +---+ | | F: 2 inputs and 0 outputs
* | B | | G | | F | G: 2 inputs and 1 output
* | | 7 +---+ 8 | | 9 | | H: 2 inputs and 2 outputs
* | |-->| D |-->| |--------->| |
* +---+ +---+ +---+ +---+
*
* @author Hadrien GRASLAND
* @date 2017-10-05
*/
// ============================================================================
// You may notice that in the graph above, all algorithms will be implemented
// in a similar way. All they do is to make sure that inputs and outputs are
// declared properly, and send/check dummy data around.
//
// So ideally, we would like to write a single algorithm template that we can
// instantiate at will for each of our eight test algorithms, like this:
//
// using AlgE = DataflowTestAlg<{2}, {3, 6}>;
//
// However, this is not valid C++ today, and we will need to go through some
// ugly metaprogramming tricks to get something similar.
//
// Let's start with what C++ does provides today, namely std::integer_sequence.
// That name is too long for humans to type repeatedly, so let's shorten it.
//
template <int... ids>
using IDs = std::integer_sequence<int, ids...>;
//
// Can we now make sure that algorithms get instantiated using syntax like
// "using AlgE = DataflowTestAlg<IDs<2>, IDs<3, 6>>"? Yes, we can. But due to
// more unfortunate C++ limitations, we will also need to use specialization
// for this purpose instead of a pure template declaration.
//
template <typename Inputs, typename Outputs>
class DataHandleFlowAlg;
//
template <int... inputIDs, int... outputIDs>
class DataHandleFlowAlg<IDs<inputIDs...>, IDs<outputIDs...>> : public Algorithm
{
public:
// NOTE: Cannot use "using Algorithm::Algorithm;" due to a GCC 6 bug
template <typename... Args>
DataHandleFlowAlg( Args&&... args ) : Algorithm( std::forward<Args>( args )... )
{
}
/// Run the algorithm, checking the data flow graph and I/O values
StatusCode execute() override
{
// TODO: Once reentrant Algorithms land in Gaudi, use their explicit context
auto eventContext = getContext();
// Check that inputs were declared correctly
using AccessMode = Gaudi::v2::DataHandle::AccessMode;
info() << "Checking input keys" << endmsg;
DataObjIDColl expectedInputKeys;
for ( const auto& inputHandle : m_readers ) {
expectedInputKeys.emplace( inputHandle.targetKey() );
}
if ( dataDependencies( AccessMode::Read ) != expectedInputKeys ) {
throw std::runtime_error( "Unexpected input keys" );
}
// Check that outputs were declared correctly
info() << "Checking output keys" << endmsg;
DataObjIDColl expectedOutputKeys;
for ( const auto& outputHandle : m_writers ) {
expectedOutputKeys.emplace( outputHandle.targetKey() );
}
if ( dataDependencies( AccessMode::Write ) != expectedOutputKeys ) {
throw std::runtime_error( "Unexpected output keys" );
}
// Check that we receive the expected input values
info() << "Checking input values" << endmsg;
for ( size_t i = 0; i < N_inputs; ++i ) {
if ( m_readers[i].get( eventContext ) != m_inputValues[i] ) {
throw std::runtime_error( "Unexpected input #" + std::to_string( i ) );
}
}
// Send our output values
info() << "Sending output values" << endmsg;
for ( size_t i = 0; i < N_outputs; ++i ) {
const auto& output = m_outputValues[i];
if ( m_writers[i].put( eventContext, output ) != output ) {
throw std::runtime_error( "Unexpected output #" + std::to_string( i ) );
}
}
return StatusCode::SUCCESS;
}
private:
// Count our inputs and outputs
static constexpr size_t N_inputs = sizeof...( inputIDs );
static constexpr size_t N_outputs = sizeof...( outputIDs );
// Record our input and output IDs for future use as input and output values
std::array<int, N_inputs> m_inputValues{inputIDs...};
std::array<int, N_outputs> m_outputValues{outputIDs...};
// Declare our inputs using ReadHandles
using IntReadHandle = Gaudi::EventReadHandle<int>;
std::array<IntReadHandle, N_inputs> m_readers{IntReadHandle{
this, "Input" + std::to_string( inputIDs ), DataObjID( "/Event/Int" + std::to_string( inputIDs ) )}...};
// Declare our outputs using WriteHandles
using IntWriteHandle = Gaudi::EventWriteHandle<int>;
std::array<IntWriteHandle, N_outputs> m_writers{IntWriteHandle{
this, "Output" + std::to_string( outputIDs ), DataObjID( "/Event/Int" + std::to_string( outputIDs ) )}...};
};
// clang-format off
// Generate the algorithms used by the test | Inputs | Outputs |
using DataHandleFlowAlgA = DataHandleFlowAlg< IDs<>, IDs<1> >;
using DataHandleFlowAlgB = DataHandleFlowAlg< IDs<>, IDs<4, 7> >;
using DataHandleFlowAlgC = DataHandleFlowAlg< IDs<3>, IDs<> >;
using DataHandleFlowAlgD = DataHandleFlowAlg< IDs<7>, IDs<8> >;
using DataHandleFlowAlgE = DataHandleFlowAlg< IDs<2>, IDs<3, 6> >;
using DataHandleFlowAlgF = DataHandleFlowAlg< IDs<6, 9>, IDs<> >;
using DataHandleFlowAlgG = DataHandleFlowAlg< IDs<5, 8>, IDs<9> >;
using DataHandleFlowAlgH = DataHandleFlowAlg< IDs<1, 4>, IDs<2, 5> >;
// clang-format on
// Now, make the algorithms accessible via Python
#define DECLARE_COMPONENT_ALIAS( Alg ) DECLARE_COMPONENT_WITH_ID( Alg, #Alg )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgA )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgB )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgC )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgD )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgE )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgF )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgG )
DECLARE_COMPONENT_ALIAS( DataHandleFlowAlgH )
#include "GaudiKernel/Algorithm.h"
#include "GaudiKernel/DataObjID.h"
#include "GaudiKernel/EventDataHandle.h"
#include <memory>
#include <stdexcept>
#include <string>
// ============================================================================
/** @file
* Test harness checking that new DataHandles access the whiteboard correctly
*
* @author Hadrien GRASLAND
* @date 2017-10-05
*/
// ============================================================================
// === TEST DATA ===
/// Handles must correctly pass around plain-of-data, non-DataObject types
const int TEST_INT = 042;
const std::string TEST_STRING = "Should work as well";
/// Since the current Handle implementation treats DataObjects specially, it
/// must also be tested with payloads that inherit from DataObject, whether
/// that data is movable and normally passed around by value...
class DoubleObject : public DataObject
{
public:
DoubleObject( double value ) : m_value( value ) {}
bool operator!=( const DoubleObject& other ) const { return ( m_value != other.m_value ); }
private:
double m_value;
};
const DoubleObject TEST_DOUBLE_OBJ = DoubleObject( 713705.37 );
/// ...or not movable, and passed around using a special pointer-to-DataObject
/// code path (that will be removed after code migration is finished).
class NonMovableObject : public DataObject
{
public:
// This type is neither movable nor copyable, as happens sometimes
NonMovableObject( NonMovableObject&& ) = delete;
NonMovableObject( const NonMovableObject& ) = delete;
NonMovableObject& operator=( NonMovableObject&& ) = delete;
NonMovableObject& operator=( const NonMovableObject& ) = delete;
// Fundamentally, all it does is to wrap a simpler value type
NonMovableObject( size_t value ) : m_value( value ) {}
size_t value() const { return m_value; }
private:
size_t m_value;
};
const size_t TEST_NON_MOVABLE_VALUE = 0xbadbeef;
// === TEST ALGORITHMS ===
/// This algorithm tests supported whiteboard writing scenarios
class WriteHandleTestAlg : public Algorithm
{
public:
// NOTE: Cannot use "using Algorithm::Algorithm;" due to a GCC 6 bug
template <typename... Args>
WriteHandleTestAlg( Args&&... args ) : Algorithm( std::forward<Args>( args )... )
{
}
StatusCode execute() override
{
// TODO: Once reentrant Algorithms land in Gaudi, use their explicit context
auto eventContext = getContext();
// Test that putting integers in works
info() << "Writing an int" << endmsg;
const auto& intVal = m_intWriter.put( eventContext, TEST_INT );
if ( intVal != TEST_INT ) {
throw std::runtime_error( "Bad output from writing an int" );
}
// Test that putting strings in works
info() << "Writing a string" << endmsg;
const auto& stringVal = m_stringWriter.put( eventContext, TEST_STRING );
if ( stringVal != TEST_STRING ) {
throw std::runtime_error( "Bad output from writing a string" );
}
// Test that putting DataObject values in works
info() << "Writing a DataObject by value" << endmsg;
const auto& doubleVal = m_objWriter.put( eventContext, TEST_DOUBLE_OBJ );
if ( doubleVal != TEST_DOUBLE_OBJ ) {
throw std::runtime_error( "Bad output from writing a DataObject value" );
}
// Test that putting non-movable DataObjects in via a pointer works
info() << "Writing a DataObject by pointer" << endmsg;
auto objPtr = std::make_unique<NonMovableObject>( TEST_NON_MOVABLE_VALUE );
const auto& objVal = m_objPtrWriter.put( eventContext, std::move( objPtr ) );
if ( objVal.value() != TEST_NON_MOVABLE_VALUE ) {
throw std::runtime_error( "Bad output from writing a DataObject pointer" );
}
return StatusCode::SUCCESS;
}
private:
template <typename T>
using WriteHandle = Gaudi::EventWriteHandle<T>;
/// We can write plain old data, like ints...
WriteHandle<int> m_intWriter{this, "IntOutput", DataObjID( "/Event/MyInt" ), "Integer output"};
/// ...or strings (note that docstrings are optional)...
WriteHandle<std::string> m_stringWriter{this, "StringOutput", DataObjID( "/Event/MyString" )};
/// ...or data objects...
WriteHandle<DoubleObject> m_objWriter{this, "ObjOutput", DataObjID( "/Event/MyObj" ), "Data object output (movable)"};
/// ...or even non-movable data objects...
WriteHandle<NonMovableObject> m_objPtrWriter{this, "ObjPtrOutput", DataObjID( "/Event/MyObjPtr" ),
"Data object output (via ptr)"};
};
DECLARE_COMPONENT( WriteHandleTestAlg )
/// This algorithm tests supported whiteboard reading scenarios
class ReadHandleTestAlg : public Algorithm
{
public:
// NOTE: Cannot use "using Algorithm::Algorithm;" due to a GCC 6 bug
template <typename... Args>
ReadHandleTestAlg( Args&&... args ) : Algorithm( std::forward<Args>( args )... )
{
}
StatusCode execute() override
{
// TODO: Once reentrant Algorithms land in Gaudi, use their explicit context
auto eventContext = getContext();
// Test that fetching integers works
info() << "Reading an int" << endmsg;
const auto& intVal = m_intReader.get( eventContext );
if ( intVal != TEST_INT ) {
throw std::runtime_error( "Bad output from reading an int" );
}
// Test that fetching strings works
info() << "Reading a string" << endmsg;
const auto& stringVal = m_stringReader.get( eventContext );
if ( stringVal != TEST_STRING ) {
throw std::runtime_error( "Bad output from reading a string" );
}
// Test that fetching DataObject values works
info() << "Reading a DataObject that was inserted by value" << endmsg;
const auto& doubleVal = m_objReader.get( eventContext );
if ( doubleVal != TEST_DOUBLE_OBJ ) {
throw std::runtime_error( "Bad output from reading a DataObject value" );
}
// Test that putting non-movable DataObjects in via a pointer works
info() << "Reading a DataObject that was inserted by pointer" << endmsg;
const auto& objVal = m_objPtrReader.get( eventContext );
if ( objVal.value() != TEST_NON_MOVABLE_VALUE ) {
throw std::runtime_error( "Bad output from reading a DataObject pointer" );
}
// TODO: Once special handling of ranges is implemented, test it as well
return StatusCode::SUCCESS;
}
private:
template <typename T>
using ReadHandle = Gaudi::EventReadHandle<T>;
/// We can read plain old data, like ints...
ReadHandle<int> m_intReader{this, "IntInput", DataObjID( "/Event/MyInt" ), "Integer input"};
/// ...or strings...
ReadHandle<std::string> m_stringReader{this, "StringInput", DataObjID( "/Event/MyString" ), "String input"};
/// ...or data objects (note that docstrings are optional)...
ReadHandle<DoubleObject> m_objReader{this, "ObjInput", DataObjID( "/Event/MyObj" )};
/// ...or even non-movable data objects...
ReadHandle<NonMovableObject> m_objPtrReader{this, "ObjPtrInput", DataObjID( "/Event/MyObjPtr" ),
"Data object input (via ptr)"};
};
DECLARE_COMPONENT( ReadHandleTestAlg )
......@@ -20,13 +20,13 @@ StatusCode IncidentAsyncTestAlg::initialize()
// Copied from CPUCruncher.cpp
for ( auto k : m_inpKeys ) {
debug() << "adding input key " << k << endmsg;
m_inputObjHandles.emplace_back( new DataObjectHandle<DataObject>( k, Gaudi::v1::DataHandle::Reader, this ) );
m_inputObjHandles.emplace_back( new DataObjectHandle<DataObject>( k, Gaudi::DataHandle::Reader, this ) );
declare( *m_inputObjHandles.back() );
}
for ( auto k : m_outKeys ) {
debug() << "adding output key " << k << endmsg;
m_outputObjHandles.emplace_back( new DataObjectHandle<DataObject>( k, Gaudi::v1::DataHandle::Writer, this ) );
m_outputObjHandles.emplace_back( new DataObjectHandle<DataObject>( k, Gaudi::DataHandle::Writer, this ) );
declare( *m_outputObjHandles.back() );
}
......
<?xml version="1.0" ?><!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'>
<extension class="GaudiTest.GaudiExeTest" kind="test">
<argument name="program"><text>gaudirun.py</text></argument>
<argument name="options"><text>
from Gaudi.Configuration import *
from Configurables import (HiveWhiteBoard, HiveSlimEventLoopMgr,
AvalancheSchedulerSvc, AlgResourcePool,
DataHandleFlowAlgA, DataHandleFlowAlgB,
DataHandleFlowAlgC, DataHandleFlowAlgD,
DataHandleFlowAlgE, DataHandleFlowAlgF,
DataHandleFlowAlgG, DataHandleFlowAlgH)
# Example configuration. We only need to check that data dependencies are fed
# to the Scheduler, so threaded execution is unnecessary unpredictability.
evtMax = 2
evtslots = 1
threads = 1
# Boring GaudiHive setup
whiteboard = HiveWhiteBoard("EventDataSvc", EventSlots=evtslots)
slimeventloopmgr = HiveSlimEventLoopMgr(SchedulerName="AvalancheSchedulerSvc",
OutputLevel=DEBUG)
scheduler = AvalancheSchedulerSvc(ThreadPoolSize=threads,
OutputLevel=WARNING)
AlgResourcePool(OutputLevel=DEBUG)
# A nontrivial Algorithm data flow graph. Notice that this is _not_ a correct
# execution order: the Scheduler should figure out a correct order using the
# data dependency information that the DataHandles provide.
algs = [DataHandleFlowAlgA(), DataHandleFlowAlgB(), DataHandleFlowAlgC(),
DataHandleFlowAlgD(), DataHandleFlowAlgE(), DataHandleFlowAlgF(),
DataHandleFlowAlgG(), DataHandleFlowAlgH()]
# Application manager setup
ApplicationMgr(EvtMax=evtMax,
EvtSel='NONE',
ExtSvc=[whiteboard],
EventLoop=slimeventloopmgr,
TopAlg=algs,
MessageSvcType="InertMessageSvc")
</text></argument>
<argument name="reference"><text>refs/data_handles_dataflow.ref</text></argument>
<argument name="exit_code"><integer>0</integer></argument>
</extension>
<?xml version="1.0" ?><!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'>
<extension class="GaudiTest.GaudiExeTest" kind="test">
<argument name="program"><text>gaudirun.py</text></argument>
<argument name="options"><text>
from Gaudi.Configuration import *
from Configurables import ReadHandleTestAlg, WriteHandleTestAlg
from Configurables import EvtDataSvc
EvtDataSvc("EventDataSvc").RootCLID = 1
EvtDataSvc("EventDataSvc").RootName = "/Event"
EvtDataSvc("EventDataSvc").ForceLeaves = True
wa = WriteHandleTestAlg(IntOutput="/Event/Hey")
ra = ReadHandleTestAlg(IntInput="/Event/Hey")
app = ApplicationMgr(TopAlg = [wa, ra],
EvtSel = "NONE",
EvtMax = 2)
</text></argument>
<argument name="reference"><text>refs/data_handles_getput.ref</text></argument>
<argument name="exit_code"><integer>0</integer></argument>
</extension>
# setting LC_ALL to "C"
# --> Including file '/tmp/tmpv8_i_h.py'
# <-- End of file '/tmp/tmpv8_i_h.py'
MessageSvc INFO Activating in a separate thread
ApplicationMgr SUCCESS
====================================================================================================================================
Welcome to ApplicationMgr (GaudiCoreSvc v30r2)
running on linux-2ak3 on Fri Jun 8 14:24:51 2018
====================================================================================================================================
ApplicationMgr INFO Application Manager Configured successfully
HiveSlimEventLo... DEBUG Property update for OutputLevel : new value = 2
HiveSlimEventLo... DEBUG Service base class initialized successfully
ThreadPoolSvc INFO no thread init tools attached
AlgResourcePool DEBUG Property update for OutputLevel : new value = 2
AlgResourcePool DEBUG Service base class initialized successfully
AlgResourcePool INFO TopAlg list empty. Recovering the one of Application Manager
AlgResourcePool DEBUG List of algorithms is:
AlgResourcePool DEBUG o DataHandleFlowAlgA/DataHandleFlowAlgA @ 0x557f11020fa0
AlgResourcePool DEBUG o DataHandleFlowAlgB/DataHandleFlowAlgB @ 0x557f11023d50
AlgResourcePool DEBUG o DataHandleFlowAlgC/DataHandleFlowAlgC @ 0x557f11025150
AlgResourcePool DEBUG o DataHandleFlowAlgD/DataHandleFlowAlgD @ 0x557f110264c0
AlgResourcePool DEBUG o DataHandleFlowAlgE/DataHandleFlowAlgE @ 0x557f11027950
AlgResourcePool DEBUG o DataHandleFlowAlgF/DataHandleFlowAlgF @ 0x557f11028fb0
AlgResourcePool DEBUG o DataHandleFlowAlgG/DataHandleFlowAlgG @ 0x557f1102a420
AlgResourcePool DEBUG o DataHandleFlowAlgH/DataHandleFlowAlgH @ 0x557f1102ba80
PrecedenceSvc INFO Assembling CF and DF task precedence rules
PrecedenceSvc INFO PrecedenceSvc initialized successfully
HiveSlimEventLo...WARNING Unable to locate service "EventSelector"
HiveSlimEventLo...WARNING No events will be processed from external input.
HistogramPersis...WARNING Histograms saving not required.
HiveSlimEventLo... INFO Found 0 events in black list
ApplicationMgr INFO Application Manager Initialized successfully
ApplicationMgr INFO Application Manager Started successfully
HiveSlimEventLo... INFO Starting loop on events
HiveSlimEventLo... DEBUG work loop iteration 0
HiveSlimEventLo... DEBUG createdEvts: 0, freeslots: 1
HiveSlimEventLo... DEBUG work loop iteration 1
HiveSlimEventLo... DEBUG Draining the scheduler
HiveSlimEventLo... DEBUG Waiting for a context
DataHandleFlowAlgA INFO Checking input keys
DataHandleFlowAlgA INFO Checking output keys
DataHandleFlowAlgA INFO Checking input values
DataHandleFlowAlgA INFO Sending output values
DataHandleFlowAlgB INFO Checking input keys
DataHandleFlowAlgB INFO Checking output keys
DataHandleFlowAlgB INFO Checking input values
DataHandleFlowAlgB INFO Sending output values
DataHandleFlowAlgD INFO Checking input keys
DataHandleFlowAlgD INFO Checking output keys
DataHandleFlowAlgD INFO Checking input values
DataHandleFlowAlgD INFO Sending output values
DataHandleFlowAlgH INFO Checking input keys
DataHandleFlowAlgH INFO Checking output keys
DataHandleFlowAlgH INFO Checking input values
DataHandleFlowAlgH INFO Sending output values
DataHandleFlowAlgE INFO Checking input keys
DataHandleFlowAlgE INFO Checking output keys
DataHandleFlowAlgE INFO Checking input values
DataHandleFlowAlgE INFO Sending output values
DataHandleFlowAlgC INFO Checking input keys
DataHandleFlowAlgC INFO Checking output keys
DataHandleFlowAlgC INFO Checking input values