pyembed.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "platform.h"
  14. #include "Python.h"
  15. #include "eclrtl.hpp"
  16. #include "jexcept.hpp"
  17. #include "jthread.hpp"
  18. #include "hqlplugins.hpp"
  19. #ifdef _WIN32
  20. #define EXPORT __declspec(dllexport)
  21. #else
  22. #define EXPORT
  23. #endif
  24. static const char * compatibleVersions[] = {
  25. "Python2.7 Embed Helper 1.0.0",
  26. NULL };
  27. static const char *version = "Python2.7 Embed Helper 1.0.0";
  28. static const char * EclDefinition =
  29. "EXPORT Language := SERVICE\n"
  30. " boolean getEmbedContext():cpp,pure,namespace='pyembed',entrypoint='getEmbedContext',prototype='IEmbedContext* getEmbedContext()';\n"
  31. " boolean syntaxCheck(const varstring src):cpp,pure,namespace='pyembed',entrypoint='syntaxCheck';\n"
  32. "END;"
  33. "export getEmbedContext := Language.getEmbedContext;"
  34. "export syntaxCheck := Language.syntaxCheck;";
  35. extern "C" EXPORT bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
  36. {
  37. if (pb->size == sizeof(ECLPluginDefinitionBlockEx))
  38. {
  39. ECLPluginDefinitionBlockEx * pbx = (ECLPluginDefinitionBlockEx *) pb;
  40. pbx->compatibleVersions = compatibleVersions;
  41. }
  42. else if (pb->size != sizeof(ECLPluginDefinitionBlock))
  43. return false;
  44. pb->magicVersion = PLUGIN_VERSION;
  45. pb->version = version;
  46. pb->moduleName = "python";
  47. pb->ECL = EclDefinition;
  48. pb->flags = PLUGIN_DLL_MODULE | PLUGIN_MULTIPLE_VERSIONS;
  49. pb->description = "Python2.7 Embed Helper";
  50. return true;
  51. }
  52. namespace pyembed {
  53. // Use class OwnedPyObject for any objects that are not 'borrowed references'
  54. // so that the appropriate Py_DECREF call is made when the OwnedPyObject goes
  55. // out of scope, even if the function returns prematurely (such as via an exception).
  56. // In particular, checkPythonError is a lot easier to call safely if this is used.
  57. class OwnedPyObject
  58. {
  59. PyObject *ptr;
  60. public:
  61. inline OwnedPyObject() : ptr(NULL) {}
  62. inline OwnedPyObject(PyObject *_ptr) : ptr(_ptr) {}
  63. inline ~OwnedPyObject() { if (ptr) Py_DECREF(ptr); }
  64. inline PyObject * get() const { return ptr; }
  65. inline PyObject * operator -> () const { return ptr; }
  66. inline operator PyObject *() const { return ptr; }
  67. inline void clear() { if (ptr) Py_DECREF(ptr); ptr = NULL; }
  68. inline void setown(PyObject *_ptr) { clear(); ptr = _ptr; }
  69. };
  70. // call checkPythonError to throw an exception if Python error state is set
  71. static void checkPythonError()
  72. {
  73. PyObject* err = PyErr_Occurred();
  74. if (err)
  75. {
  76. OwnedPyObject errStr = PyObject_Str(err);
  77. PyErr_Clear();
  78. rtlFail(0, PyString_AsString(errStr));
  79. }
  80. }
  81. // The Python Global Interpreter Lock (GIL) won't know about C++-created threads, so we need to
  82. // call PyGILState_Ensure() and PyGILState_Release at the start and end of every function.
  83. // Wrapping them in a class like this ensures that the release always happens even if
  84. // the function exists prematurely
  85. class GILstateWrapper
  86. {
  87. PyGILState_STATE gstate;
  88. public:
  89. GILstateWrapper()
  90. {
  91. gstate = PyGILState_Ensure();
  92. }
  93. ~GILstateWrapper()
  94. {
  95. PyGILState_Release(gstate);
  96. }
  97. };
  98. // There is a singleton PythonThreadContext per thread. This allows us to
  99. // ensure that we can make repeated calls to a Python function efficiently.
  100. class PythonThreadContext
  101. {
  102. public:
  103. PyThreadState *threadState;
  104. public:
  105. PythonThreadContext()
  106. {
  107. threadState = PyEval_SaveThread();
  108. locals.setown(PyDict_New());
  109. globals.setown(PyDict_New());
  110. }
  111. ~PythonThreadContext()
  112. {
  113. locals.clear();
  114. globals.clear();
  115. script.clear();
  116. result.clear();
  117. PyEval_RestoreThread(threadState);
  118. }
  119. inline void bindRealParam(const char *name, double val)
  120. {
  121. OwnedPyObject vval = PyFloat_FromDouble(val);
  122. PyDict_SetItemString(locals, name, vval);
  123. }
  124. inline void bindSignedParam(const char *name, __int64 val)
  125. {
  126. OwnedPyObject vval = PyLong_FromLongLong(val);
  127. PyDict_SetItemString(locals, name, vval);
  128. }
  129. inline void bindUnsignedParam(const char *name, unsigned __int64 val)
  130. {
  131. OwnedPyObject vval = PyLong_FromUnsignedLongLong(val);
  132. PyDict_SetItemString(locals, name, vval);
  133. }
  134. inline void bindStringParam(const char *name, size32_t len, const char *val)
  135. {
  136. OwnedPyObject vval = PyString_FromStringAndSize(val, len);
  137. PyDict_SetItemString(locals, name, vval);
  138. }
  139. inline void bindVStringParam(const char *name, const char *val)
  140. {
  141. OwnedPyObject vval = PyString_FromString(val);
  142. PyDict_SetItemString(locals, name, vval);
  143. }
  144. inline double getRealResult()
  145. {
  146. assertex(result);
  147. return (__int64) PyFloat_AsDouble(result);
  148. }
  149. inline __int64 getSignedResult()
  150. {
  151. assertex(result);
  152. return (__int64) PyLong_AsLongLong(result);
  153. }
  154. inline unsigned __int64 getUnsignedResult()
  155. {
  156. return (__int64) PyLong_AsUnsignedLongLong(result);
  157. }
  158. inline void getStringResult(size32_t &__len, char * &__result)
  159. {
  160. assertex(result);
  161. __len = PyString_Size(result);
  162. const char * chars = PyString_AsString(result);
  163. checkPythonError();
  164. __result = (char *)rtlMalloc(__len);
  165. memcpy(__result, chars, __len);
  166. }
  167. inline void compileEmbeddedScript(const char *text)
  168. {
  169. if (!prevtext || strcmp(text, prevtext) != 0)
  170. {
  171. prevtext.clear();
  172. script.setown(Py_CompileString(text, "", Py_eval_input));
  173. checkPythonError();
  174. prevtext.set(text);
  175. }
  176. }
  177. inline void callFunction()
  178. {
  179. checkPythonError();
  180. result.setown(PyEval_EvalCode((PyCodeObject *) script.get(), locals, globals));
  181. checkPythonError();
  182. }
  183. private:
  184. GILstateWrapper GILState;
  185. OwnedPyObject locals;
  186. OwnedPyObject globals;
  187. OwnedPyObject script;
  188. OwnedPyObject result;
  189. StringAttr prevtext;
  190. };
  191. // Each call to a Python function will use a new Python27EmbedFunctionContext object
  192. // This takes care of ensuring that the Python GIL is locked while we are executing python code,
  193. // and released when we are not
  194. class Python27EmbedFunctionContext : public CInterfaceOf<IEmbedFunctionContext>
  195. {
  196. public:
  197. Python27EmbedFunctionContext(PythonThreadContext *_sharedCtx)
  198. : sharedCtx(_sharedCtx)
  199. {
  200. PyEval_RestoreThread(sharedCtx->threadState);
  201. }
  202. ~Python27EmbedFunctionContext()
  203. {
  204. sharedCtx->threadState = PyEval_SaveThread();
  205. }
  206. virtual void bindRealParam(const char *name, double val)
  207. {
  208. return sharedCtx->bindRealParam(name, val);
  209. }
  210. virtual void bindSignedParam(const char *name, __int64 val)
  211. {
  212. return sharedCtx->bindSignedParam(name, val);
  213. }
  214. virtual void bindUnsignedParam(const char *name, unsigned __int64 val)
  215. {
  216. return sharedCtx->bindUnsignedParam(name, val);
  217. }
  218. virtual void bindStringParam(const char *name, size32_t len, const char *val)
  219. {
  220. return sharedCtx->bindStringParam(name, len, val);
  221. }
  222. virtual void bindVStringParam(const char *name, const char *val)
  223. {
  224. return sharedCtx->bindVStringParam(name, val);
  225. }
  226. virtual double getRealResult()
  227. {
  228. return sharedCtx->getRealResult();
  229. }
  230. virtual __int64 getSignedResult()
  231. {
  232. return sharedCtx->getSignedResult();
  233. }
  234. virtual unsigned __int64 getUnsignedResult()
  235. {
  236. return sharedCtx->getUnsignedResult();
  237. }
  238. virtual void getStringResult(size32_t &__len, char * &__result)
  239. {
  240. sharedCtx->getStringResult(__len, __result);
  241. }
  242. virtual void compileEmbeddedScript(const char *text)
  243. {
  244. sharedCtx->compileEmbeddedScript(text);
  245. }
  246. virtual void callFunction()
  247. {
  248. sharedCtx->callFunction();
  249. }
  250. private:
  251. PythonThreadContext *sharedCtx;
  252. };
  253. __thread PythonThreadContext* threadContext; // We reuse per thread, for speed
  254. __thread ThreadTermFunc threadHookChain;
  255. static void releaseContext()
  256. {
  257. delete threadContext;
  258. if (threadHookChain)
  259. (*threadHookChain)();
  260. }
  261. static class Python27EmbedContext : public CInterfaceOf<IEmbedContext>
  262. {
  263. public:
  264. Python27EmbedContext()
  265. {
  266. // Initialize the Python Interpreter
  267. Py_Initialize();
  268. PyEval_InitThreads();
  269. tstate = PyEval_SaveThread();
  270. Link(); // Deliberately 'leak' in order to avoid freeing this global object prematurely
  271. }
  272. ~Python27EmbedContext()
  273. {
  274. PyEval_RestoreThread(tstate);
  275. // Finish the Python Interpreter
  276. Py_Finalize();
  277. }
  278. virtual IEmbedFunctionContext *createFunctionContext()
  279. {
  280. if (!threadContext)
  281. {
  282. threadContext = new PythonThreadContext;
  283. threadHookChain = addThreadTermFunc(releaseContext);
  284. }
  285. return new Python27EmbedFunctionContext(threadContext);
  286. }
  287. protected:
  288. PyThreadState *tstate;
  289. } theEmbedContext;
  290. extern IEmbedContext* getEmbedContext()
  291. {
  292. return LINK(&theEmbedContext);
  293. }
  294. extern bool syntaxCheck(const char *script)
  295. {
  296. return true; // MORE
  297. }
  298. } // namespace