return cls
+def assert_no_new_pyobjects_executing_eagerly(f):
+ """Decorator for asserting that no new Python objects persist after a test.
+
+ Runs the test multiple times executing eagerly, first as a warmup and then
+ several times to let objects accumulate. The warmup helps ignore caches which
+ do not grow as the test is run repeatedly.
+
+ Useful for checking that there are no missing Py_DECREFs in the C exercised by
+ a bit of Python.
+ """
+ def decorator(self, **kwargs):
+ """Warms up, gets an object count, runs the test, checks for new objects."""
+ with context.eager_mode():
+ gc.disable()
+ f(self, **kwargs)
+ gc.collect()
+ previous_count = len(gc.get_objects())
+ for _ in range(3):
+ f(self, **kwargs)
+ gc.collect()
+ # There should be no new Python objects hanging around.
+ new_count = len(gc.get_objects())
+ self.assertEqual(previous_count, new_count)
+ gc.enable()
+ return decorator
+
def assert_no_new_tensors(f):
"""Decorator for asserting that no new Tensors persist after a test.
LeakedTensorTest().test_has_no_leak()
+ def test_no_new_objects_decorator(self):
+
+ class LeakedObjectTest(object):
+
+ def __init__(inner_self): # pylint: disable=no-self-argument
+ inner_self.assertEqual = self.assertEqual # pylint: disable=invalid-name
+ inner_self.accumulation = []
+
+ @test_util.assert_no_new_pyobjects_executing_eagerly
+ def test_has_leak(self):
+ self.accumulation.append([1.])
+
+ @test_util.assert_no_new_pyobjects_executing_eagerly
+ def test_has_no_leak(self):
+ self.not_accumulating = [1.]
+
+ with self.assertRaises(AssertionError):
+ LeakedObjectTest().test_has_leak()
+
+ LeakedObjectTest().test_has_no_leak()
if __name__ == "__main__":
googletest.main()
from tensorflow.python.framework import importer
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_shape
+from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gradient_checker
from tensorflow.python.ops import logging_ops
shape=[2, 3, 5])
self.assertEqual(c.get_shape(), [2, 3, 5])
+ @test_util.assert_no_new_pyobjects_executing_eagerly
+ def testEagerMemory(self):
+ """Tests PyObject refs are managed correctly when executing eagerly."""
+ constant_op.constant([[1.]])
+
def testImplicitShapeNumPy(self):
with ops.Graph().as_default():
c = constant_op.constant(
from __future__ import print_function
import collections
-import gc
import numpy as np
self.assertEqual(dense.kernel.name, 'my_dense/kernel:0')
self.assertEqual(dense.bias.name, 'my_dense/bias:0')
+ @test_util.assert_no_new_pyobjects_executing_eagerly
def testNoEagerLeak(self):
# Tests that repeatedly constructing and building a Layer does not leak
# Python objects.
- def _test_fn():
- inputs = random_ops.random_uniform((5, 4), seed=1)
- core_layers.Dense(5)(inputs)
- core_layers.Dense(2, activation=nn_ops.relu, name='my_dense')(inputs)
-
- with context.eager_mode():
- _test_fn() # warmup
- gc.disable()
- gc.collect()
- object_count = len(gc.get_objects())
- for _ in range(100):
- _test_fn()
- gc.collect()
- self.assertLessEqual(
- len(gc.get_objects()),
- # DEBUG_SAVEALL messes with this slightly.
- object_count + 1)
- gc.enable()
+ inputs = random_ops.random_uniform((5, 4), seed=1)
+ core_layers.Dense(5)(inputs)
+ core_layers.Dense(2, activation=nn_ops.relu, name='my_dense')(inputs)
@test_util.run_in_graph_and_eager_modes()
def testCallTensorDot(self):
}
Status InferShapeAndType(PyObject* obj, TensorShape* shape, DataType* dtype) {
+ std::vector<Safe_PyObjectPtr> refs_to_clean;
while (true) {
// We test strings first, in case a string is considered a sequence.
if (IsPyString(obj)) {
if (length > 0) {
shape->AddDim(length);
obj = PySequence_GetItem(obj, 0);
+ refs_to_clean.push_back(make_safe(obj));
continue;
} else if (length == 0) {
shape->AddDim(length);
if (shape.dims() > 1) { \
/* Iterate over outer dim, and recursively convert each element. */ \
const int64 s = shape.dim_size(0); \
- if (TF_PREDICT_FALSE(s != PySequence_Length(obj))) { \
+ Safe_PyObjectPtr seq = make_safe(PySequence_Fast(obj, "")); \
+ if (TF_PREDICT_FALSE(s != PySequence_Fast_GET_SIZE(seq.get()))) { \
return ErrorRectangular; \
} \
TensorShape rest = shape; \
rest.RemoveDim(0); \
for (int64 i = 0; i < s; ++i) { \
- const char* error = \
- FUNCTION##Helper(PySequence_GetItem(obj, i), rest, buf); \
+ const char* error = FUNCTION##Helper( \
+ PySequence_Fast_GET_ITEM(seq.get(), i), rest, buf); \
if (TF_PREDICT_FALSE(error != nullptr)) return error; \
} \
} else { \