Update of /cvsroot/pywin32/pywin32/isapi/src
In directory ddv4jf1.ch3.sourceforge.com:/tmp/cvs-serv25245
Modified Files:
PyExtensionObjects.cpp PyExtensionObjects.h
Log Message:
add support for io completion callbacks and fix HSE_EXEC_URL_INFO support
Index: PyExtensionObjects.h
===================================================================
RCS file: /cvsroot/pywin32/pywin32/isapi/src/PyExtensionObjects.h,v
retrieving revision 1.6
retrieving revision 1.7
diff -C2 -d -r1.6 -r1.7
*** PyExtensionObjects.h 29 Sep 2008 13:00:37 -0000 1.6
--- PyExtensionObjects.h 30 Dec 2008 12:20:20 -0000 1.7
***************
*** 28,32 ****
--- 28,37 ----
#include "ControlBlock.h"
+ #ifdef WRITE_RESTRICTED
+ #undef WRITE_RESTRICTED
+ #endif
#include "structmember.h"
+ // avoid anyone accidently using the wrong WRITE_RESTRICTED...
+ #undef WRITE_RESTRICTED
#include "tupleobject.h"
***************
*** 101,105 ****
static PyObject * IsKeepConn(PyObject *self, PyObject * args); // HSE_REQ_IS_KEEP_CONN
static PyObject * SetFlushFlag(PyObject *self, PyObject * args); // HSE_REQ_SET_FLUSH_FLAG
! static PyObject * ExecURLInfo(PyObject *self, PyObject * args); // HSE_REQ_EXEC_URL
static PyObject * IsSessionActive(PyObject *self, PyObject * args);
--- 106,111 ----
static PyObject * IsKeepConn(PyObject *self, PyObject * args); // HSE_REQ_IS_KEEP_CONN
static PyObject * SetFlushFlag(PyObject *self, PyObject * args); // HSE_REQ_SET_FLUSH_FLAG
! static PyObject * ExecURL(PyObject *self, PyObject * args); // HSE_REQ_EXEC_URL
! static PyObject * ReqIOCompletion(PyObject *self, PyObject * args); // HSE_REQ_IO_COMPLETION
static PyObject * IsSessionActive(PyObject *self, PyObject * args);
Index: PyExtensionObjects.cpp
===================================================================
RCS file: /cvsroot/pywin32/pywin32/isapi/src/PyExtensionObjects.cpp,v
retrieving revision 1.10
retrieving revision 1.11
diff -C2 -d -r1.10 -r1.11
*** PyExtensionObjects.cpp 29 Sep 2008 13:00:37 -0000 1.10
--- PyExtensionObjects.cpp 30 Dec 2008 12:20:20 -0000 1.11
***************
*** 29,32 ****
--- 29,128 ----
#include "PythonEng.h"
+ // Asynch IO callbacks are a little tricky, as we never know how many
+ // callbacks a single connection might make (often each callback will trigger
+ // another IO request.) So we keep the Python objects used by the callback
+ // mechanism in a map, keyed by connection ID, of asynch callback handlers.
+ // Each element is a tuple of (callback, user_args). This is done rather than
+ // using the 'pContext' param of the callback to ensure lifetimes of the
+ // callback and the 'user arg' are maintained correctly. The item is removed
+ // as the HSE_REQ_DONE_WITH_SESSION callback is made, or of the callback
+ // raises an exception.
+ static PyObject *g_callbackMap = NULL;
+
+ BOOL SetupIOCallback(EXTENSION_CONTROL_BLOCK *ecb, PyObject *ob)
+ {
+ if (!g_callbackMap) {
+ if (!(g_callbackMap = PyDict_New()))
+ return FALSE;
+ }
+ PyObject *key = PyLong_FromVoidPtr(ecb->ConnID);
+ if (!key)
+ return FALSE;
+ if (0!=PyDict_SetItem(g_callbackMap, key, ob)) {
+ Py_DECREF(key);
+ return FALSE;
+ }
+ Py_DECREF(key);
+ return TRUE;
+ }
+
+ void CleanupIOCallback(EXTENSION_CONTROL_BLOCK *ecb)
+ {
+ if (!g_callbackMap)
+ return;
+ PyObject *key = PyLong_FromVoidPtr(ecb->ConnID);
+ if (!key)
+ return; // ack - not much more we can do.
+ if (!PyDict_DelItem(g_callbackMap, key))
+ PyErr_Clear();
+ Py_DECREF(key);
+ return;
+ }
+
+ #define CALLBACK_ERROR(msg) {ExtensionError(NULL, msg);goto done;}
+
+ extern "C" void WINAPI DoIOCallback(EXTENSION_CONTROL_BLOCK *ecb, PVOID pContext, DWORD cbIO, DWORD dwError)
+ {
+ CEnterLeavePython _celp;
+ CControlBlock * pcb = NULL;
+ PyECB *pyECB = NULL;
+ BOOL worked = FALSE;
+ if (!g_callbackMap)
+ CALLBACK_ERROR("Callback when no callback map exists");
+
+ PyObject *key = PyLong_FromVoidPtr(ecb->ConnID);
+ if (!key)
+ CALLBACK_ERROR("Failed to create map key from connection ID");
+ PyObject *ob = PyDict_GetItem(g_callbackMap, key);
+ if (!ob)
+ CALLBACK_ERROR("Failed to locate map entry for this commID");
+ // get the Python ECB object...
+ pcb = new CControlBlock(ecb);
+ pyECB = new PyECB(pcb);
+ if (!pyECB || !pcb)
+ CALLBACK_ERROR("Failed to create Python oject for ECB");
+
+ // this should be impossible...
+ if (!PyTuple_Check(ob) || (PyTuple_Size(ob)!=1 && PyTuple_Size(ob)!=2))
+ CALLBACK_ERROR("Object in callback map not a tuple of correct size?");
+
+ PyObject *callback = PyTuple_GET_ITEM(ob, 0);
+ PyObject *user_arg = PyTuple_Size(ob)==2 ? PyTuple_GET_ITEM(ob, 1) : Py_None;
+ PyObject *args = Py_BuildValue("(OOkk)", pyECB, user_arg, cbIO, dwError);
+ if (!args)
+ CALLBACK_ERROR("Failed to build callback args");
+ PyObject *result = PyObject_Call(callback, args, NULL);
+ Py_DECREF(args);
+ if (!result)
+ CALLBACK_ERROR("Callback failed");
+ Py_DECREF(result);
+ worked = TRUE;
+ done:
+ // If the callback failed, then its likely this request will end
+ // up hanging. So on error we nuke ourselves from the map then
+ // call DoneWithSession. We still hold the GIL, so we should be
+ // safe from races...
+ Py_XDECREF(pyECB);
+ if (!worked) {
+ // free the item from the map.
+ CleanupIOCallback(ecb);
+ // clobber the callback.
+ ecb->ServerSupportFunction(ecb->ConnID, HSE_REQ_IO_COMPLETION, NULL, NULL,NULL);
+ // and tell IIS there we are done with an error.
+ DWORD status = HSE_STATUS_ERROR;
+ ecb->ServerSupportFunction(ecb->ConnID, HSE_REQ_DONE_WITH_SESSION, &status, NULL, 0);
+ }
+ }
+
// @doc
// @object HSE_VERSION_INFO|An object used by ISAPI GetExtensionVersion
***************
*** 124,128 ****
// EXTENSION_CONTROL_BLOCK.
struct memberlist PyECB::PyECB_memberlist[] = {
! {"Version", T_INT, ECBOFF(m_version), READONLY},
{"ConnID", T_INT, ECBOFF(m_connID), READONLY},
--- 220,225 ----
// EXTENSION_CONTROL_BLOCK.
struct memberlist PyECB::PyECB_memberlist[] = {
! {"Version", T_INT, ECBOFF(m_version), READONLY},
! // XXX - ConnID 64bit issue?
{"ConnID", T_INT, ECBOFF(m_connID), READONLY},
***************
*** 158,162 ****
{"GetImpersonationToken", PyECB::GetImpersonationToken, 1}, // @pymeth GetImpersonationToken|
{"IsKeepConn", PyECB::IsKeepConn, 1}, // @pymeth IsKeepConn|Calls ServerSupportFunction with HSE_REQ_IS_KEEP_CONN
! {"ExecURLInfo", PyECB::ExecURLInfo, 1}, // @pymeth ExecURLInfo|Calls ServerSupportFunction with HSE_REQ_EXEC_URL
{NULL}
};
--- 255,260 ----
{"GetImpersonationToken", PyECB::GetImpersonationToken, 1}, // @pymeth GetImpersonationToken|
{"IsKeepConn", PyECB::IsKeepConn, 1}, // @pymeth IsKeepConn|Calls ServerSupportFunction with HSE_REQ_IS_KEEP_CONN
! {"ExecURL", PyECB::ExecURL, 1}, // @pymeth ExecURL|Calls ServerSupportFunction with HSE_REQ_EXEC_URL
! {"ReqIOCompletion", PyECB::ReqIOCompletion, 1}, // @pymeth ReqIOCompletion|Calls ServerSupportFunction with HSE_REQ_IO_COMPLETION
{NULL}
};
***************
*** 576,585 ****
}
! // @pymethod int|EXTENSION_CONTROL_BLOCK|ExecURLInfo|Calls ServerSupportFunction with HSE_REQ_EXEC_URL
! PyObject * PyECB::ExecURLInfo(PyObject *self, PyObject *args)
{
PyObject *obInfo, *obEntity;
HSE_EXEC_URL_INFO i;
! if (!PyArg_ParseTuple(args, "zzzOOi:ExecURLInfo",
&i.pszUrl, // @pyparm string|url||
&i.pszMethod, // @pyparm string|method||
--- 674,685 ----
}
! // @pymethod int|EXTENSION_CONTROL_BLOCK|ExecURL|Calls ServerSupportFunction with HSE_REQ_EXEC_URL
! // @comm This function is only available in IIS6 and later.
! PyObject * PyECB::ExecURL(PyObject *self, PyObject *args)
{
PyObject *obInfo, *obEntity;
HSE_EXEC_URL_INFO i;
! memset(&i, 0, sizeof(i)); // to be sure, to be sure...
! if (!PyArg_ParseTuple(args, "zzzOOi:ExecURL",
&i.pszUrl, // @pyparm string|url||
&i.pszMethod, // @pyparm string|method||
***************
*** 598,610 ****
EXTENSION_CONTROL_BLOCK *ecb = pecb->m_pcb->GetECB();
if (!pecb || !pecb->Check()) return NULL;
! BOOL bRes, bIs;
Py_BEGIN_ALLOW_THREADS
bRes = ecb->ServerSupportFunction(ecb->ConnID, HSE_REQ_EXEC_URL, &i, NULL,NULL);
Py_END_ALLOW_THREADS
if (!bRes)
! return SetPyECBError("ServerSupportFunction(HSE_REQ_EXEC_URL)");
! return PyBool_FromLong(bIs);
}
class PyTFD {
--- 698,744 ----
EXTENSION_CONTROL_BLOCK *ecb = pecb->m_pcb->GetECB();
if (!pecb || !pecb->Check()) return NULL;
! BOOL bRes;
Py_BEGIN_ALLOW_THREADS
bRes = ecb->ServerSupportFunction(ecb->ConnID, HSE_REQ_EXEC_URL, &i, NULL,NULL);
Py_END_ALLOW_THREADS
if (!bRes)
! return SetPyECBError("ServerSupportFunction(HSE_REQ_EXEC_URL)");
! Py_RETURN_NONE;
}
+ // @pymethod int|EXTENSION_CONTROL_BLOCK|ReqIOCompletion|Set a callback that will be used for handling asynchronous I/O operations.
+ // @comm If you call this multiple times, the previous callback will be discarded.
+ // @comm A reference to the callback and args are held until <om
+ // EXTENSION_CONTROL_BLOCK.DoneWithSession> is called. If the callback
+ // function fails, DoneWithSession(HSE_STATUS_ERROR) will automatically be
+ // called and no further callbacks for the ECB will be made.
+ PyObject * PyECB::ReqIOCompletion(PyObject *self, PyObject *args)
+ {
+ PyECB * pecb = (PyECB *) self;
+ if (!pecb || !pecb->Check()) return NULL;
+ EXTENSION_CONTROL_BLOCK *ecb = pecb->m_pcb->GetECB();
+
+ PyObject *obCallback;
+ PyObject *obArg = NULL;
+ if (!PyArg_ParseTuple(args, "O|O:ReqIOCompletion",
+ &obCallback, // @pyparm callable|func||The function to call.
+ &obArg))
+ return NULL;
+
+ if (!PyCallable_Check(obCallback))
+ return PyErr_Format(PyExc_TypeError, "first param must be callable");
+ // now we have checked the params just ignore them! Stick args itself
+ // in our map.
+ if (!SetupIOCallback(ecb, args))
+ return NULL;
+
+ BOOL bRes;
+ Py_BEGIN_ALLOW_THREADS
+ bRes = ecb->ServerSupportFunction(ecb->ConnID, HSE_REQ_IO_COMPLETION, DoIOCallback, NULL, NULL);
+ Py_END_ALLOW_THREADS
+ if (!bRes)
+ return SetPyECBError("ServerSupportFunction(HSE_REQ_IO_COMPLETION)");
+ Py_RETURN_NONE;
+ }
class PyTFD {
***************
*** 761,764 ****
--- 895,902 ----
return NULL;
+ // Free any resources we've allocated on behalf of this ECB - this
+ // currently means just the io-completion callback.
+ CleanupIOCallback(pecb->m_pcb->GetECB());
+
Py_BEGIN_ALLOW_THREADS
pecb->m_pcb->DoneWithSession(status);
|