From 009b900100c9f98ed6c8e5588df88a5032bbc6da Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 24 Oct 2022 23:43:50 +0200 Subject: [PATCH] Make TimeoutQueue.iter() actually expire items It is functionally fine not to, but causes objects to never be freed if iter() is the only method called on the queue (ie. no enqueue/dequeue, len(), ...) --- src/utils/structures.py | 7 +++++-- test/test_utils.py | 27 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/utils/structures.py b/src/utils/structures.py index 4d3e7a623..251edb546 100644 --- a/src/utils/structures.py +++ b/src/utils/structures.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2009, Jeremiah Fincher -# Copyright (c) 2010-2021, Valentin Lorentz +# Copyright (c) 2010-2022, Valentin Lorentz # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -349,7 +349,10 @@ class TimeoutQueue(object): return self.queue.dequeue()[1] def __iter__(self): - # We could _clearOldElements here, but what happens if someone stores + self._clearOldElements() + + # You may think re-checking _getTimeout() after we just called + # _clearOldElements is redundant, but what happens if someone stores # the resulting generator and elements that should've timed out are # yielded? Hmm? What happens then, smarty-pants? for (t, elt) in self.queue: diff --git a/test/test_utils.py b/test/test_utils.py index d3e068f0a..5e8be3c8b 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,7 +1,7 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009,2011, James McCoy -# Copyright (c) 2010-2021, Valentin Lorentz +# Copyright (c) 2010-2022, Valentin Lorentz # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -1182,6 +1182,31 @@ class TestTimeoutQueue(SupyTestCase): q.reset() self.assertFalse(1 in q) + def testClean(self): + def iter_and_next(q): + next(iter(q)) + + def contains(q): + 42 in q + + for f in (len, repr, list, iter_and_next, contains): + print(f) + with self.subTest(f=f.__name__): + q = TimeoutQueue(1) + q.enqueue(1) + timeFastForward(0.5) + q.enqueue(2) + + self.assertEqual([x for (_, x) in q.queue], [1, 2]) + f(q) + self.assertEqual([x for (_, x) in q.queue], [1, 2]) + + timeFastForward(0.6) + + self.assertEqual([x for (_, x) in q.queue], [1, 2]) # not cleaned yet + f(q) + self.assertEqual([x for (_, x) in q.queue], [2]) # now it is + class TestCacheDict(SupyTestCase): def testMaxNeverExceeded(self): max = 10