def test_reuse_closed_handles(self): from hpy.universal import _debug mod = self.make_module(""" HPyDef_METH(f, "f", f_impl, HPyFunc_O) static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) { return HPy_Dup(ctx, ctx->h_None); } HPyDef_METH(leak, "leak", leak_impl, HPyFunc_O) static HPy leak_impl(HPyContext *ctx, HPy self, HPy arg) { HPy_Dup(ctx, arg); // leak! return HPy_Dup(ctx, ctx->h_None); } @EXPORT(f) @EXPORT(leak) @INIT """) old_size = _debug.get_closed_handles_queue_max_size() try: gen = _debug.new_generation() # call f twice to open/closes a bunch of handles mod.f('hello') mod.f('world') # # make sure that the closed_handles queue is considered full: this # will force the reuse of existing closed handles _debug.set_closed_handles_queue_max_size(1) # during the call to leak, we create handles for: # 1. self # 2. arg # 3. HPy_Dup(arg) (leaking) # 4. result # So the leaked handle will be 3rd in the old closed_handles queue closed_handles = _debug.get_closed_handles() mod.leak('foo') assert not closed_handles[2].is_closed assert closed_handles[2].obj == 'foo' closed_handles = _debug.get_closed_handles() mod.leak('bar') assert not closed_handles[2].is_closed assert closed_handles[2].obj == 'bar' leaks = _debug.get_open_handles(gen) for h in leaks: h._force_close() finally: _debug.set_closed_handles_queue_max_size(old_size)
def test_closed_handles(self): from hpy.universal import _debug mod = self.make_leak_module() gen = _debug.new_generation() mod.leak('hello') h_hello, = _debug.get_open_handles(gen) assert not h_hello.is_closed h_hello._force_close() assert h_hello.is_closed assert _debug.get_open_handles(gen) == [] assert h_hello in _debug.get_closed_handles() assert repr(h_hello) == "<DebugHandle 0x%x CLOSED>" % h_hello.id
def test_closed_handles(compiler, with_alloc_trace): from hpy.universal import _debug mod = make_leak_module(compiler) gen = _debug.new_generation() mod.leak('hello') h_hello, = _debug.get_open_handles(gen) assert not h_hello.is_closed h_hello._force_close() assert h_hello.is_closed assert _debug.get_open_handles(gen) == [] assert h_hello in _debug.get_closed_handles() assert repr(h_hello).startswith("<DebugHandle 0x%x CLOSED>" % h_hello.id)
def clear_raw_data_in_closed_handles(): closed_size = len(_debug.get_closed_handles()) old_limit = _debug.get_closed_handles_queue_max_size() try: # make sure that the closed_handles queue is considered full: this # will force the reuse of existing closed handles. _debug.set_closed_handles_queue_max_size(closed_size) # Dummy call to force the reuse of the existing closed handles # -2 because 'self' and the 'arg' should already reuse 2 handles mod.g(closed_size - 2) finally: _debug.set_closed_handles_queue_max_size(old_limit) return closed_size
def test_closed_handles_queue_max_size(self): from hpy.universal import _debug mod = self.make_module(""" HPyDef_METH(f, "f", f_impl, HPyFunc_O) static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) { return HPy_Dup(ctx, ctx->h_None); } @EXPORT(f) @INIT """) old_size = _debug.get_closed_handles_queue_max_size() try: # by calling "f" we open and close 3 handles: 1 for self, 1 for arg # and 1 for the result. So, every call to f() increases the size of # closed_handles() by 3 n1 = len(_debug.get_closed_handles()) _debug.set_closed_handles_queue_max_size(n1+7) assert _debug.get_closed_handles_queue_max_size() == n1+7 # mod.f('aaa') n2 = len(_debug.get_closed_handles()) assert n2 == n1+3 # mod.f('bbb') n3 = len(_debug.get_closed_handles()) assert n3 == n2+3 # with the next call we reach the maximum size of the queue mod.f('ccc') n4 = len(_debug.get_closed_handles()) assert n4 == n1+7 # # same as before mod.f('ddd') n5 = len(_debug.get_closed_handles()) assert n5 == n1+7 finally: _debug.set_closed_handles_queue_max_size(old_size)
def test_charptr_limit_stress_test(compiler): from hpy.universal import _debug mod = compiler.make_module(""" HPyDef_METH(f, "f", f_impl, HPyFunc_O) static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) { HPy_ssize_t size; HPyUnicode_AsUTF8AndSize(ctx, arg, &size); if (size == 0) { return HPy_NULL; } return HPy_Dup(ctx, ctx->h_None); } // Dummy function just to force handle creation, but should not create // any raw data attached to those handles HPyDef_METH(g, "g", g_impl, HPyFunc_O) static HPy g_impl(HPyContext *ctx, HPy self, HPy arg) { int len = HPyLong_AsLong(ctx, arg); for (int i = 0; i < len; ++i) { HPy h = HPyLong_FromLong(ctx, i); HPy_Close(ctx, h); } return HPy_Dup(ctx, ctx->h_None); } @EXPORT(f) @EXPORT(g) @INIT """) def get_raw_data_sizes(handles): return list(map(lambda h: h.raw_data_size, handles)) def clear_raw_data_in_closed_handles(): closed_size = len(_debug.get_closed_handles()) old_limit = _debug.get_closed_handles_queue_max_size() try: # make sure that the closed_handles queue is considered full: this # will force the reuse of existing closed handles. _debug.set_closed_handles_queue_max_size(closed_size) # Dummy call to force the reuse of the existing closed handles # -2 because 'self' and the 'arg' should already reuse 2 handles mod.g(closed_size - 2) finally: _debug.set_closed_handles_queue_max_size(old_limit) return closed_size old_raw_data_max_size = _debug.get_protected_raw_data_max_size() old_closed_handles_max_size = _debug.get_closed_handles_queue_max_size() _debug.set_protected_raw_data_max_size(100) try: # Reset the state as much as possible: closed_size = clear_raw_data_in_closed_handles() # Make enough room for the handles created by this test _debug.set_closed_handles_queue_max_size(closed_size + 100) # Sanity check: no raw data is now held by closed handles initial = get_raw_data_sizes(_debug.get_closed_handles()) assert all(map(lambda x: x == -1, initial)) # Large string that shouldn't be retained at all gen = _debug.new_generation() mod.f('abc' * 50) closed1 = get_raw_data_sizes(_debug.get_closed_handles(gen)) assert closed1 == [-1, -1, -1] # -1 for 'self' and the return value # Two small strings, should be kept, one large that should not fit in gen = _debug.new_generation() mod.f('a' * 31) mod.f('b' * 32) mod.f('c' * 50) closed2 = get_raw_data_sizes(_debug.get_closed_handles(gen)) # -1 for 'self'/return value for each call, and the long string # note: C strings' size is +1 for the terminating '\0' assert sorted(closed2) == [-1] * 7 + [32, 33] # Another small string should still fit gen = _debug.new_generation() mod.f('a' * 13) closed3 = get_raw_data_sizes(_debug.get_closed_handles(gen)) # 'self'/return, and the expected small string assert sorted(closed3) == [-1, -1, 14] # But another small-ish string not anymore gen = _debug.new_generation() mod.f('a' * 27) closed4 = get_raw_data_sizes(_debug.get_closed_handles(gen)) # 'self'/return, and the string whose raw data didn't fit in assert sorted(closed4) == [-1, -1, -1] # Check that raw data of closed handles is freed when the handle # is reused closed_size = clear_raw_data_in_closed_handles() # None of the closed handles should now have any raw data attached all_closed = _debug.get_closed_handles() assert len(all_closed) == closed_size # Sanity check for h in all_closed: assert h.raw_data_size == -1 finally: _debug.set_protected_raw_data_max_size(old_raw_data_max_size) _debug.set_closed_handles_queue_max_size(old_closed_handles_max_size)