Skip to content

Commit

Permalink
The Py_Initialize and Py_Finalize should be called from the same thre…
Browse files Browse the repository at this point in the history
…ad. (#211)

* The Py_Initialize and Py_Finalize should be called from the same thread.
Ensure it by creating a dedicated thread for this.
  • Loading branch information
zefir-o committed Aug 6, 2024
1 parent d619218 commit 1031e09
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 33 deletions.
18 changes: 18 additions & 0 deletions pythonfmu/pythonfmu-export/src/pythonfmu/IPyState.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef PYTHONFMU_IPYTHONSTATE_HPP
#define PYTHONFMU_IPYTHONSTATE_HPP

namespace pythonfmu
{
class IPyState
{
public:
IPyState() = default;
IPyState(IPyState const& other) = delete;
IPyState(IPyState&& other) = delete;
IPyState& operator=(IPyState const& other) = delete;
IPyState& operator=(IPyState&& other) = delete;
virtual ~IPyState() = default;
};
}

#endif //PYTHONFMU_IPYTHONSTATE_HPP
27 changes: 19 additions & 8 deletions pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

#include "pythonfmu/IPyState.hpp"
#include "pythonfmu/PySlaveInstance.hpp"

#include "pythonfmu/PyState.hpp"
Expand All @@ -7,6 +8,7 @@

#include <fstream>
#include <functional>
#include <mutex>
#include <regex>
#include <sstream>
#include <utility>
Expand Down Expand Up @@ -44,8 +46,9 @@ inline void py_safe_run(const std::function<void(PyGILState_STATE gilState)>& f)
PyGILState_Release(gil_state);
}

PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible)
: instanceName_(std::move(instanceName))
PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible, std::shared_ptr<IPyState> pyState)
: pyState_{ std::move(pyState) }
, instanceName_(std::move(instanceName))
, resources_(std::move(resources))
, logger_(logger)
, visible_(visible)
Expand Down Expand Up @@ -536,7 +539,10 @@ PySlaveInstance::~PySlaveInstance()

} // namespace pythonfmu

std::unique_ptr<pythonfmu::PyState> pyState = nullptr;
namespace {
std::mutex weakPyStateMutex{};
std::weak_ptr<pythonfmu::PyState> weakPyState{};
}

cppfmu::UniquePtr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
cppfmu::FMIString instanceName,
Expand All @@ -562,10 +568,15 @@ cppfmu::UniquePtr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
#endif
}

if (pyState == nullptr) {
pyState = std::make_unique<pythonfmu::PyState>();
}
{
auto const getOrCreatePyState = [&]() {
auto const lock = std::lock_guard{ weakPyStateMutex };
auto const pyState = weakPyState.lock();
return nullptr != pyState ? pyState : std::make_shared<pythonfmu::PyState>();
};
auto pyState = getOrCreatePyState();

return cppfmu::AllocateUnique<pythonfmu::PySlaveInstance>(
memory, instanceName, resources, logger, visible);
return cppfmu::AllocateUnique<pythonfmu::PySlaveInstance>(
memory, instanceName, resources, logger, visible, std::move(pyState));
}
}
4 changes: 3 additions & 1 deletion pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#define PYTHONFMU_SLAVEINSTANCE_HPP

#include "cppfmu/cppfmu_cs.hpp"
#include "pythonfmu/IPyState.hpp"

#include <Python.h>
#include <string>
Expand All @@ -15,7 +16,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance
{

public:
PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, bool visible);
PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, bool visible, std::shared_ptr<IPyState> pyState);

void initialize(PyGILState_STATE gilState);

Expand Down Expand Up @@ -49,6 +50,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance
~PySlaveInstance() override;

private:
std::shared_ptr<IPyState> pyState_;
PyObject* pClass_;
PyObject* pInstance_{};
PyObject* pMessages_{};
Expand Down
90 changes: 66 additions & 24 deletions pythonfmu/pythonfmu-export/src/pythonfmu/PyState.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,84 @@
#ifndef PYTHONFMU_PYTHONSTATE_HPP
#define PYTHONFMU_PYTHONSTATE_HPP

#include "IPyState.hpp"
#include <Python.h>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

namespace pythonfmu
{

class PyState
{
public:
PyState()
class PyState : public IPyState
{
was_initialized_ = Py_IsInitialized();
public:
PyState()
: _initDeinitPyThread_{ &PyState::Worker, this }
{
auto lock = std::unique_lock{ mutex_ };
conditionalVariable_.wait(lock, [&] { return consructed_; });
}

~PyState() override
{
{
auto const lock = std::lock_guard{ mutex_ };
destroyRequested_ = true;
}
conditionalVariable_.notify_one();
if (_initDeinitPyThread_.joinable()) _initDeinitPyThread_.join();
}

private:

if (!was_initialized_) {
Py_SetProgramName(L"./PythonFMU");
Py_Initialize();
// In accordance to the documentation https://docs.python.org/3/c-api/init.html#c.Py_FinalizeEx
// the Py_Initialize/Py_Finalize should be called from the same
// thread. The FMI standard allows to call fmi functions from different threads, also different threads
// could be used for loading unloading the FMU libraries. There is a deadlock, when the simulation tool
// unloads the FMU library from the thread which is differs from the thread where the Py_Initialize was called.
// Create a new thread which is used for calling Py_Initialize and Py_Deinitialize. The thread waits for
// notification from destructor and then calls Py_Deinitialize.

void Worker()
{
// It will be nullptr in case when some other tool already called Py_IsInitialized.
// There is no need to call Py_Finalize thus, this thread can exit ASAP.
auto const mainPyThread = []() -> PyThreadState* {
auto const justInitialized = !Py_IsInitialized();
if (justInitialized) {
Py_SetProgramName(L"./PythonFMU");
Py_Initialize();
#if PY_VERSION_HEX < 0x03070000
PyEval_InitThreads();
PyEval_InitThreads();
#endif
_mainPyThread = PyEval_SaveThread();
}
}
return PyEval_SaveThread();
}
return nullptr;
}();

~PyState()
{
if (!was_initialized_) {
PyEval_RestoreThread(_mainPyThread);
Py_Finalize();
{
auto const lock = std::lock_guard{ mutex_ };
consructed_ = true;
}
conditionalVariable_.notify_one();

if (nullptr != mainPyThread) {
auto lock = std::unique_lock{ mutex_ };
conditionalVariable_.wait(lock, [&] { return destroyRequested_; });

PyEval_RestoreThread(mainPyThread);
Py_Finalize();
}
}
}

private:
bool was_initialized_;
PyThreadState* _mainPyThread;
};
bool consructed_ = false;
bool destroyRequested_ = false;
std::condition_variable conditionalVariable_;
std::mutex mutex_;
std::thread _initDeinitPyThread_;
};

} // namespace pythonfmu

#endif //PYTHONFMU_PYTHONSTATE_HPP
#endif //PYTHONFMU_PYTHONSTATE_HPP

0 comments on commit 1031e09

Please sign in to comment.