From 75e9d252006b74347db769e34f36961c0b74e07b Mon Sep 17 00:00:00 2001 From: "mhammond%skippinet.com.au" Date: Fri, 9 May 2003 01:23:22 +0000 Subject: [PATCH] Fix severe leaks when using string object, and update the test suite to try and catch this in the future. Not part of the build. git-svn-id: svn://10.0.0.236/trunk@142252 18797224-902f-48f8-a5cc-f745e15eee43 --- .../python/xpcom/src/VariantUtils.cpp | 18 ++--- .../python/xpcom/test/pyxpcom_test_tools.py | 72 +++++++++++++++++++ .../python/xpcom/test/test_test_component.py | 26 ++++++- 3 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 mozilla/extensions/python/xpcom/test/pyxpcom_test_tools.py diff --git a/mozilla/extensions/python/xpcom/src/VariantUtils.cpp b/mozilla/extensions/python/xpcom/src/VariantUtils.cpp index 975bef6335b..5cbb1671409 100644 --- a/mozilla/extensions/python/xpcom/src/VariantUtils.cpp +++ b/mozilla/extensions/python/xpcom/src/VariantUtils.cpp @@ -852,19 +852,13 @@ PyXPCOM_InterfaceVariantHelper::~PyXPCOM_InterfaceVariantHelper() } } if (ns_v.IsValDOMString() && ns_v.val.p) { - PythonTypeDescriptor &ptd = m_python_type_desc_array[i]; - if (XPT_PD_IS_OUT(ptd.param_flags) || XPT_PD_IS_DIPPER(ptd.param_flags)) - delete (const nsAString *)ns_v.val.p; + delete (const nsAString *)ns_v.val.p; } if (ns_v.IsValCString() && ns_v.val.p) { - PythonTypeDescriptor &ptd = m_python_type_desc_array[i]; - if (XPT_PD_IS_OUT(ptd.param_flags) || XPT_PD_IS_DIPPER(ptd.param_flags)) - delete (const nsACString *)ns_v.val.p; + delete (const nsACString *)ns_v.val.p; } if (ns_v.IsValUTF8String() && ns_v.val.p) { - PythonTypeDescriptor &ptd = m_python_type_desc_array[i]; - if (XPT_PD_IS_OUT(ptd.param_flags) || XPT_PD_IS_DIPPER(ptd.param_flags)) - delete (const nsACString *)ns_v.val.p; + delete (const nsACString *)ns_v.val.p; } if (ns_v.IsValArray()) { nsXPTCVariant &ns_v = m_var_array[i]; @@ -1171,6 +1165,8 @@ PRBool PyXPCOM_InterfaceVariantHelper::FillInVariant(const PythonTypeDescriptor PyErr_NoMemory(); BREAK_FALSE; } + // We created it - flag as such for cleanup. + ns_v.flags |= nsXPTCVariant::VAL_IS_DOMSTR; break; } case nsXPTType::T_CSTRING: @@ -1203,6 +1199,8 @@ PRBool PyXPCOM_InterfaceVariantHelper::FillInVariant(const PythonTypeDescriptor PyErr_NoMemory(); BREAK_FALSE; } + // We created it - flag as such for cleanup. + ns_v.flags |= bIsUTF8 ? nsXPTCVariant::VAL_IS_UTF8STR : nsXPTCVariant::VAL_IS_CSTR; break; } case nsXPTType::T_CHAR_STR: { @@ -1455,6 +1453,7 @@ PRBool PyXPCOM_InterfaceVariantHelper::PrepareOutVariant(const PythonTypeDescrip ns_v.flags |= nsXPTCVariant::VAL_IS_DOMSTR; // Dippers are really treated like "in" params. ns_v.ptr = new nsString(); + ns_v.val.p = ns_v.ptr; // VAL_IS_* says the .p is what gets freed if (!ns_v.ptr) { PyErr_NoMemory(); rc = PR_FALSE; @@ -1467,6 +1466,7 @@ PRBool PyXPCOM_InterfaceVariantHelper::PrepareOutVariant(const PythonTypeDescrip NS_ABORT_IF_FALSE(XPT_PD_IS_DIPPER(td.param_flags) && XPT_PD_IS_IN(td.param_flags), "out DOMStrings must really be in dippers!"); ns_v.flags |= ( XPT_TDP_TAG(ns_v.type)==nsXPTType::T_CSTRING ? nsXPTCVariant::VAL_IS_CSTR : nsXPTCVariant::VAL_IS_UTF8STR); ns_v.ptr = new nsCString(); + ns_v.val.p = ns_v.ptr; // VAL_IS_* says the .p is what gets freed if (!ns_v.ptr) { PyErr_NoMemory(); rc = PR_FALSE; diff --git a/mozilla/extensions/python/xpcom/test/pyxpcom_test_tools.py b/mozilla/extensions/python/xpcom/test/pyxpcom_test_tools.py new file mode 100644 index 00000000000..765e08a9c14 --- /dev/null +++ b/mozilla/extensions/python/xpcom/test/pyxpcom_test_tools.py @@ -0,0 +1,72 @@ +# test tools for the pyxpcom + +# export a "getmemusage()" function that returns a useful "bytes used" count +# for the current process. Growth in this when doing the same thing over and +# over implies a leak. + +try: + import win32api + import win32pdh + import win32pdhutil + have_pdh = 1 +except ImportError: + have_pdh = 0 + +# XXX - win32pdh is slow, particularly finding our current process. +# A better way would be good. + +# Our win32pdh specific functions - they can be at the top-level on all +# platforms, but will only actually be called if the modules are available. +def FindMyCounter(): + pid_me = win32api.GetCurrentProcessId() + + object = "Process" + items, instances = win32pdh.EnumObjectItems(None,None,object, -1) + for instance in instances: + # We use 2 counters - "ID Process" and "Working Set" + counter = "ID Process" + format = win32pdh.PDH_FMT_LONG + + hq = win32pdh.OpenQuery() + path = win32pdh.MakeCounterPath( (None,object,instance, None, -1,"ID Process") ) + hc1 = win32pdh.AddCounter(hq, path) + path = win32pdh.MakeCounterPath( (None,object,instance, None, -1,"Working Set") ) + hc2 = win32pdh.AddCounter(hq, path) + win32pdh.CollectQueryData(hq) + type, pid = win32pdh.GetFormattedCounterValue(hc1, format) + if pid==pid_me: + win32pdh.RemoveCounter(hc1) # not needed any more + return hq, hc2 + # Not mine - close the query and try again + win32pdh.RemoveCounter(hc1) + win32pdh.RemoveCounter(hc2) + win32pdh.CloseQuery(hq) + else: + raise RuntimeError, "Can't find myself!?" + +def CloseCounter(hq, hc): + win32pdh.RemoveCounter(hc) + win32pdh.CloseQuery(hq) + +def GetCounterValue(hq, hc): + win32pdh.CollectQueryData(hq) + format = win32pdh.PDH_FMT_LONG + type, val = win32pdh.GetFormattedCounterValue(hc, format) + return val + +g_pdh_data = None +# The pdh function that does the work +def pdh_getmemusage(): + global g_pdh_data + if g_pdh_data is None: + hq, hc = FindMyCounter() + g_pdh_data = hq, hc + hq, hc = g_pdh_data + return GetCounterValue(hq, hc) + +# The public bit +if have_pdh: + getmemusage = pdh_getmemusage +else: + def getmemusage(): + return 0 diff --git a/mozilla/extensions/python/xpcom/test/test_test_component.py b/mozilla/extensions/python/xpcom/test/test_test_component.py index 5781ecb59e8..c56685b4ec4 100644 --- a/mozilla/extensions/python/xpcom/test/test_test_component.py +++ b/mozilla/extensions/python/xpcom/test/test_test_component.py @@ -15,11 +15,16 @@ # Contributor(s): Mark Hammond (original author) # -import sys, os +import sys, os, time import xpcom.components import xpcom._xpcom import xpcom.nsError +try: + import gc +except ImportError: + gc = None + num_errors = 0 component_iid = xpcom.components.ID("{7EE4BDC6-CB53-42c1-A9E4-616B8E012ABA}") @@ -453,6 +458,8 @@ except ImportError: def gettotalrefcount(): return 0 +from pyxpcom_test_tools import getmemusage + def test_from_js(): # Ensure we can find the js test script - same dir as this! # Assume the path of sys.argv[0] is where we can find the js test code. @@ -492,11 +499,17 @@ def doit(num_loops = -1): if i==0: # First loop is likely to "leak" as we cache things. # Leaking after that is a problem. + if gc is not None: + gc.collect() num_refs = gettotalrefcount() + mem_usage = getmemusage() if num_errors: break + if gc is not None: + gc.collect() + lost = gettotalrefcount() - num_refs # Sometimes we get spurious counts off by 1 or 2. # This can't indicate a real leak, as we have looped @@ -504,6 +517,17 @@ def doit(num_loops = -1): if abs(lost)>2: print "*** Lost %d references" % (lost,) + # sleep to allow the OS to recover + time.sleep(1) + mem_lost = getmemusage() - mem_usage + # working set size is fickle, and when we were leaking strings, this test + # would report a leak of 100MB. So we allow a 2MB buffer - but even this + # may still occasionally report spurious warnings. If you are really + # worried, bump the counter to a huge value, and if there is a leak it will + # show. + if mem_lost > 2000000: + print "*** Lost %.6f MB of memory" % (mem_lost/1000000.0,) + if num_errors: print "There were", num_errors, "errors testing the Python component :-(" else: