From bb0fa65122fe8654b0d717a4c37038d883bc69d0 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Tue, 28 Nov 2017 12:02:18 +0100
Subject: [PATCH] ENH: respond to externalCoupled lock file contents

- waitForSlave now return a Time::stopAtControls enumeration:

    unknown:     when lockfile has no specially recognized content.
    endTime:     when lockfile contains "status=done"
    writeNow:    when lockfile contains "action=writeNow"
    nextWrite:   when lockfile contains "action=nextWrite"
    noWriteNow:  when lockfile contains "action=noWriteNow"

These values can be used by the caller to terminate the master
(OpenFOAM) as desired in response to information placed there by the
slave process.
---
 .../general/coupling/externalFileCoupler.C    | 102 +++++++++++++-----
 .../general/coupling/externalFileCoupler.H    |  81 +++++++++-----
 .../field/externalCoupled/externalCoupled.C   |  72 +++++++------
 .../field/externalCoupled/externalCoupled.H   |   5 +-
 ...edPointDisplacementPointPatchVectorField.C |  19 +++-
 5 files changed, 186 insertions(+), 93 deletions(-)

diff --git a/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.C b/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.C
index 7d891b058f1..8bc524fdcc4 100644
--- a/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.C
+++ b/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.C
@@ -40,17 +40,51 @@ namespace Foam
 Foam::word Foam::externalFileCoupler::lockName = "OpenFOAM";
 
 
+namespace Foam
+{
+
 // file-scope
-// check file (must exist) for "status=done" content
-static bool checkIsDone(const std::string& lck)
+// Read file contents and return a stop control as follows:
+// - contains "done" (should actually be status=done, but we are generous) :
+//   The master (OpenFOAM) has signalled that it is done. Report as <endTime>
+//
+// - action=writeNow, action=nextWrite action=noWriteNow :
+//   The slave has signalled that it is done and wants the master to exit with
+//   the specified type of action. Report as corresponding <action>.
+//
+// Anything else (empty file, no action=, etc) is reported as <unknown>.
+//
+static enum Time::stopAtControls getStopAction(const std::string& filename)
 {
-    std::string content;
-    std::ifstream is(lck);
-    is >> content;
+    // Slurp entire input file (must exist) as a single string
+    std::string fileContent;
+
+    std::ifstream is(filename);
+    std::getline(is, fileContent, '\0');
+
+    if (fileContent.find("done") != std::string::npos)
+    {
+        return Time::stopAtControls::saEndTime;
+    }
+
+    const auto equals = fileContent.find('=');
+
+    if (equals != std::string::npos)
+    {
+        const word actionName(word::validate(fileContent.substr(equals+1)));
+
+        return
+            Time::stopAtControlNames
+            (
+                actionName,
+                Time::stopAtControls::saUnknown
+            );
+    }
 
-    return (content.find("done") != std::string::npos);
+    return Time::stopAtControls::saUnknown;
 }
 
+} // End namespace Foam
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
@@ -141,7 +175,8 @@ bool Foam::externalFileCoupler::readDict(const dictionary& dict)
 }
 
 
-void Foam::externalFileCoupler::useMaster(const bool wait) const
+enum Foam::Time::stopAtControls
+Foam::externalFileCoupler::useMaster(const bool wait) const
 {
     const bool wasInit = initialized();
     runState_ = MASTER;
@@ -163,18 +198,20 @@ void Foam::externalFileCoupler::useMaster(const bool wait) const
 
             std::ofstream os(lck);
             os << "status=openfoam\n";
-            os.flush();
         }
     }
 
     if (wait)
     {
-        waitForMaster();
+        return waitForMaster();
     }
+
+    return Time::stopAtControls::saUnknown;
 }
 
 
-void Foam::externalFileCoupler::useSlave(const bool wait) const
+enum Foam::Time::stopAtControls
+Foam::externalFileCoupler::useSlave(const bool wait) const
 {
     const bool wasInit = initialized();
     runState_ = SLAVE;
@@ -194,19 +231,23 @@ void Foam::externalFileCoupler::useSlave(const bool wait) const
 
     if (wait)
     {
-        waitForSlave();
+        return waitForSlave();
     }
+
+    return Time::stopAtControls::saUnknown;
 }
 
 
-bool Foam::externalFileCoupler::waitForMaster() const
+enum Foam::Time::stopAtControls
+Foam::externalFileCoupler::waitForMaster() const
 {
     if (!initialized())
     {
         useMaster(); // was not initialized
     }
 
-    bool isDone = false;
+    auto action = Time::stopAtControls::saUnknown;
+
     if (Pstream::master())
     {
         const fileName lck(lockFile());
@@ -221,9 +262,11 @@ bool Foam::externalFileCoupler::waitForMaster() const
             if (prevTime < modTime)
             {
                 prevTime = modTime;
-                isDone = checkIsDone(lck);
-                if (isDone)
+
+                if (Time::stopAtControls::saEndTime == getStopAction(lck))
                 {
+                    // Found 'done' - slave should not wait for master
+                    action = Time::stopAtControls::saEndTime;
                     break;
                 }
             }
@@ -231,21 +274,24 @@ bool Foam::externalFileCoupler::waitForMaster() const
         }
     }
 
-    // MPI barrier
-    Pstream::scatter(isDone);
+    label intAction(action);
+
+    Pstream::scatter(intAction); // Also acts as MPI barrier
 
-    return !isDone;
+    return Time::stopAtControls(intAction);
 }
 
 
-bool Foam::externalFileCoupler::waitForSlave() const
+enum Foam::Time::stopAtControls
+Foam::externalFileCoupler::waitForSlave() const
 {
     if (!initialized())
     {
         useSlave(); // was not initialized
     }
 
-    bool isDone = false;
+    auto action = Time::stopAtControls::saUnknown;
+
     if (Pstream::master())
     {
         const fileName lck(lockFile());
@@ -267,16 +313,16 @@ bool Foam::externalFileCoupler::waitForSlave() const
             Log << type() << ": wait time = " << totalTime << endl;
         }
 
-        // But check for status=done content in the file
-        isDone = checkIsDone(lck);
+        action = getStopAction(lck);
 
         Log << type() << ": found lock file " << lck << endl;
     }
 
-    // MPI barrier
-    Pstream::scatter(isDone);
+    label intAction(action);
+
+    Pstream::scatter(intAction); // Also acts as MPI barrier
 
-    return !isDone;
+    return Time::stopAtControls(intAction);
 }
 
 
@@ -308,12 +354,10 @@ void Foam::externalFileCoupler::shutdown() const
 {
     if (Pstream::master() && runState_ == MASTER && Foam::isDir(commsDir_))
     {
-        const fileName lck(lockFile());
-
         Log << type() << ": lock file status=done" << endl;
-        std::ofstream os(lck);
+
+        std::ofstream os(lockFile());
         os  << "status=done\n";
-        os.flush();
     }
 
     runState_ = DONE;   // Avoid re-triggering in destructor
diff --git a/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.H b/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.H
index 7979a784e1a..6a4dfc303b1 100644
--- a/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.H
+++ b/src/finiteVolume/cfdTools/general/coupling/externalFileCoupler.H
@@ -60,21 +60,26 @@ Description
     A typical coupling loop would look like this (on the master-side):
     \verbatim
         initialize - master takes control
-        write data for slave
-        use slave, wait for slave
-        cleanup old data from master
-        read data from slave
-        use master
+        writeDataMaster() - write data for slave
+        useSlave()
+        waitForSlave()
+        removeDataMaster() - cleanup old data from master [optional?]
+        readDataMaster() - read data from slave
+        useMaster()
     \endverbatim
 
     On the slave-side:
     \verbatim
-        wait for master
-        read data from master
-        write data for master
-        use master
+        waitForMaster()
+        readDataSlave()  - read data from master
+        writeDataSlave() - write data for master
+        useMaster()
     \endverbatim
 
+    Note that since the waitForSlave() method not only waits for the lock file
+    to be reinstated but also does a simple check of its contents, it can
+    also serve to communicate some control from the slave to the master.
+
 SourceFiles
     externalFileCoupler.C
     externalFileCouplerI.H
@@ -86,6 +91,7 @@ SourceFiles
 
 #include "fileName.H"
 #include "dictionary.H"
+#include "Time.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -177,7 +183,7 @@ public:
 
     // Member Functions
 
-      // Initialization
+    // Initialization
 
         //- True if state has been initialized
         inline bool initialized() const;
@@ -186,7 +192,7 @@ public:
         inline bool slaveFirst() const;
 
 
-      // File locations
+    // File locations
 
         //- Return the file path to the base communications directory
         inline const fileName& commDirectory() const;
@@ -198,36 +204,57 @@ public:
         inline fileName lockFile() const;
 
 
-      // Settings
+    // Settings
 
         //- Read communication settings from dictionary
         bool readDict(const dictionary& dict);
 
 
-      // Handshaking
+    // Handshaking
 
         //- Create lock file to indicate that OpenFOAM is in charge
-        //  Optionally wait for master to complete as well.
-        void useMaster(const bool wait=false) const;
+        //  \param wait - wait for master to complete.
+        //
+        //  \return Time::stopAtControls::saUnknown if not waiting, otherwise
+        //      as per the waitForMaster() description.
+        enum Time::stopAtControls useMaster(const bool wait=false) const;
 
         //- Remove lock file to indicate that the external program is in charge
-        //  Optionally wait for slave to complete as well.
-        void useSlave(const bool wait=false) const;
+        //  \param wait - wait for slave to complete.
+        //
+        //  \return Time::stopAtControls::saUnknown if not waiting, otherwise
+        //      as per the waitForSlave() description.
+        enum Time::stopAtControls useSlave(const bool wait=false) const;
 
 
         //- Wait for master to complete.
         //  This is when the lock file disappears, or exists but has
-        //  "status=done" content.
-        //  \return False if lock file contains "status=done"
-        bool waitForMaster() const;
+        //  \c status=done content.
+        //
+        //  \return Time::stopAtControls::saUnknown or if lock file
+        //      contained \c status=done it returns
+        //      Time::stopAtControls::saEndTime
+        enum Time::stopAtControls waitForMaster() const;
 
         //- Wait for slave to complete.
         //  This is when the lock file appears.
-        //  \return False if lock file contains "status=done"
-        bool waitForSlave() const;
-
-
-      // File creation, removal
+        //
+        //  When the lock file appears, it is checked for the following
+        //  content which corresponds to particular return values:
+        //  - \c status=done
+        //  \return Foam::Time::saEndTime
+        //  - \c action=writeNow
+        //  \return Foam::Time::saWriteNow
+        //  - \c action=nextWrite
+        //  \return Foam::Time::saNextWrite
+        //  - \c action=noNextWrite
+        //  \return Foam::Time::saNoNextWrite
+        // - Anything else (empty file, no action= or status=, etc)
+        //  \return Foam::Time::saUnknown
+        enum Time::stopAtControls waitForSlave() const;
+
+
+    // File creation, removal
 
         //- Read data files on master (OpenFOAM).
         //  These data files are normally created by the slave.
@@ -251,13 +278,13 @@ public:
         //- Remove data files written by slave (external program)
         virtual void removeDataSlave() const;
 
-        //- Generate status=done in lock (only when run-state = master)
+
+        //- Generate \c status=done in lock (only when run-state = master)
         void shutdown() const;
 
         //- Remove files written by OpenFOAM
         void removeDirectory() const;
 
-
 };
 
 
diff --git a/src/functionObjects/field/externalCoupled/externalCoupled.C b/src/functionObjects/field/externalCoupled/externalCoupled.C
index 5f710e33186..5d18e58c335 100644
--- a/src/functionObjects/field/externalCoupled/externalCoupled.C
+++ b/src/functionObjects/field/externalCoupled/externalCoupled.C
@@ -460,6 +460,42 @@ void Foam::functionObjects::externalCoupled::initCoupling()
 }
 
 
+void Foam::functionObjects::externalCoupled::performCoupling()
+{
+    // Ensure coupling has been initialised
+    initCoupling();
+
+    // Write data for external source
+    writeDataMaster();
+
+    // Signal external source to execute (by removing lock file)
+    // - Wait for slave to provide data
+    useSlave();
+
+    // Wait for response - and catch any abort information sent from slave
+    const auto action = waitForSlave();
+
+    // Remove old data files from OpenFOAM
+    removeDataMaster();
+
+    // Read data passed back from external source
+    readDataMaster();
+
+    // Signal external source to wait (by creating the lock file)
+    useMaster();
+
+    // Process any abort information sent from slave
+    if
+    (
+        action != time_.stopAt()
+     && action != Time::stopAtControls::saUnknown
+    )
+    {
+        time_.stopAt(action);
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::functionObjects::externalCoupled::externalCoupled
@@ -483,46 +519,16 @@ Foam::functionObjects::externalCoupled::externalCoupled
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::functionObjects::externalCoupled::~externalCoupled()
-{}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 bool Foam::functionObjects::externalCoupled::execute()
 {
     if (!initialisedCoupling_ || time_.timeIndex() % calcFrequency_ == 0)
     {
-        // Initialise the coupling
-        initCoupling();
-
-        // Write data for external source
-        writeDataMaster();
-
-        // Signal external source to execute (by removing lock file)
-        // - Wait for slave to provide data
-        useSlave();
-
-        // Wait for response
-        waitForSlave();
-
-        // Remove old data files from OpenFOAM
-        removeDataMaster();
-
-        // Read data passed back from external source
-        readDataMaster();
-
-        // Signal external source to wait (by creating the lock file)
-        useMaster();
-
-        return true;
-    }
-    else
-    {
-        return false;
+        performCoupling();
     }
+
+    return false;
 }
 
 
diff --git a/src/functionObjects/field/externalCoupled/externalCoupled.H b/src/functionObjects/field/externalCoupled/externalCoupled.H
index e0059d9bf1a..516d7c4d520 100644
--- a/src/functionObjects/field/externalCoupled/externalCoupled.H
+++ b/src/functionObjects/field/externalCoupled/externalCoupled.H
@@ -282,6 +282,9 @@ private:
 
         static void checkOrder(const wordList& regionNames);
 
+        //- Perform the coupling with necessary initialization etc.
+        void performCoupling();
+
         //- Disallow default bitwise copy constructor
         externalCoupled(const externalCoupled&) = delete;
 
@@ -313,7 +316,7 @@ public:
 
 
     //- Destructor
-    virtual ~externalCoupled();
+    virtual ~externalCoupled() = default;
 
 
     // Member Functions
diff --git a/src/lumpedPointMotion/lumpedPointDisplacementPointPatchVectorField.C b/src/lumpedPointMotion/lumpedPointDisplacementPointPatchVectorField.C
index 91ce90ca712..3368d6f59f5 100644
--- a/src/lumpedPointMotion/lumpedPointDisplacementPointPatchVectorField.C
+++ b/src/lumpedPointMotion/lumpedPointDisplacementPointPatchVectorField.C
@@ -187,6 +187,8 @@ void Foam::lumpedPointDisplacementPointPatchVectorField::updateCoeffs()
         return;
     }
 
+    enum Time::stopAtControls action = Time::stopAtControls::saUnknown;
+
     const bool masterPatch = (movement().ownerId() == this->patch().index());
     if (masterPatch)
     {
@@ -250,13 +252,14 @@ void Foam::lumpedPointDisplacementPointPatchVectorField::updateCoeffs()
             {
                 movement().writeData(forces, moments);
 
-                // signal external source to execute
+                // Signal external source to execute
                 movement().coupler().useSlave();
             }
         }
 
-        // Wait for slave to provide data - includes MPI barrier
-        movement().coupler().waitForSlave();
+        // Wait for slave to provide data (includes MPI barrier)
+        // and catch any abort information sent from slave
+        action = movement().coupler().waitForSlave();
 
         // Read data passed back from external source - includes MPI barrier
         const_cast<lumpedPointMovement&>(movement()).readState();
@@ -271,6 +274,16 @@ void Foam::lumpedPointDisplacementPointPatchVectorField::updateCoeffs()
     this->operator==(tdisp);
 
     fixedValuePointPatchField<vector>::updateCoeffs();
+
+    // Process any abort information sent from slave
+    if
+    (
+        action != this->db().time().stopAt()
+     && action != Time::stopAtControls::saUnknown
+    )
+    {
+        this->db().time().stopAt(action);
+    }
 }
 
 
-- 
GitLab