[tor-commits] [stem/master] Don't use @lru_cache for exit policy rules

atagar at torproject.org atagar at torproject.org
Mon Mar 17 16:23:42 UTC 2014


commit 863ddacb961ffe255f02eeea115515bd7f3ffd41
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Mar 17 09:19:32 2014 -0700

    Don't use @lru_cache for exit policy rules
    
    Lession learned today: @lru_cache is handy for efficiency but don't rely on the
    cache to function.
    
    Our ExitPolicy class expected its _get_rules() method to be called exactly once
    due to having cached results. However, pickling then unpicking an ExitPolicy
    class caused our @lru_cache to be cleared, resulting in a TypeError.
    
    Replacing this method's @lru_cache with a standard one, and adding a test for
    pickleability. Thanks to Nicholas Hopper for the catch!
---
 stem/exit_policy.py             |   88 ++++++++++++++++++++-------------------
 test/unit/exit_policy/policy.py |   16 +++++++
 2 files changed, 62 insertions(+), 42 deletions(-)

diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 169eb13..78db793 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -167,6 +167,8 @@ class ExitPolicy(object):
     else:
       self._input_rules = rules
 
+    self._rules = None
+
     # Result when no rules apply. According to the spec policies default to 'is
     # allowed', but our microdescriptor policy subclass might want to change
     # this.
@@ -295,54 +297,56 @@ class ExitPolicy(object):
 
     return (label_prefix + ", ".join(display_ranges)).strip()
 
-  @lru_cache()
   def _get_rules(self):
-    rules = []
-    is_all_accept, is_all_reject = True, True
-
-    if isinstance(self._input_rules, bytes):
-      decompressed_rules = zlib.decompress(self._input_rules).split(b',')
-    else:
-      decompressed_rules = self._input_rules
-
-    for rule in decompressed_rules:
-      if isinstance(rule, bytes):
-        rule = stem.util.str_tools._to_unicode(rule)
-
-      if isinstance(rule, unicode):
-        rule = ExitPolicyRule(rule.strip())
+    if self._rules is None:
+      rules = []
+      is_all_accept, is_all_reject = True, True
 
-      if rule.is_accept:
-        is_all_reject = False
+      if isinstance(self._input_rules, bytes):
+        decompressed_rules = zlib.decompress(self._input_rules).split(b',')
       else:
-        is_all_accept = False
+        decompressed_rules = self._input_rules
 
-      rules.append(rule)
+      for rule in decompressed_rules:
+        if isinstance(rule, bytes):
+          rule = stem.util.str_tools._to_unicode(rule)
 
-      if rule.is_address_wildcard() and rule.is_port_wildcard():
-        break  # this is a catch-all, no reason to include more
+        if isinstance(rule, unicode):
+          rule = ExitPolicyRule(rule.strip())
 
-    # If we only have one kind of entry *and* end with a wildcard then
-    # we might as well use the simpler version. For instance...
-    #
-    #   reject *:80, reject *:443, reject *:*
-    #
-    # ... could also be represented as simply...
-    #
-    #   reject *:*
-    #
-    # This mostly comes up with reject-all policies because the
-    # 'reject private:*' appends an extra seven rules that have no
-    # effect.
-
-    if rules and (rules[-1].is_address_wildcard() and rules[-1].is_port_wildcard()):
-      if is_all_accept:
-        rules = [ExitPolicyRule("accept *:*")]
-      elif is_all_reject:
-        rules = [ExitPolicyRule("reject *:*")]
-
-    self._input_rules = None
-    return rules
+        if rule.is_accept:
+          is_all_reject = False
+        else:
+          is_all_accept = False
+
+        rules.append(rule)
+
+        if rule.is_address_wildcard() and rule.is_port_wildcard():
+          break  # this is a catch-all, no reason to include more
+
+      # If we only have one kind of entry *and* end with a wildcard then
+      # we might as well use the simpler version. For instance...
+      #
+      #   reject *:80, reject *:443, reject *:*
+      #
+      # ... could also be represented as simply...
+      #
+      #   reject *:*
+      #
+      # This mostly comes up with reject-all policies because the
+      # 'reject private:*' appends an extra seven rules that have no
+      # effect.
+
+      if rules and (rules[-1].is_address_wildcard() and rules[-1].is_port_wildcard()):
+        if is_all_accept:
+          rules = [ExitPolicyRule("accept *:*")]
+        elif is_all_reject:
+          rules = [ExitPolicyRule("reject *:*")]
+
+      self._rules = rules
+      self._input_rules = None
+
+    return self._rules
 
   def __iter__(self):
     for rule in self._get_rules():
diff --git a/test/unit/exit_policy/policy.py b/test/unit/exit_policy/policy.py
index 161bae0..041f81e 100644
--- a/test/unit/exit_policy/policy.py
+++ b/test/unit/exit_policy/policy.py
@@ -2,6 +2,7 @@
 Unit tests for the stem.exit_policy.ExitPolicy class.
 """
 
+import pickle
 import unittest
 
 from stem.exit_policy import get_config_policy, \
@@ -216,3 +217,18 @@ class TestExitPolicy(unittest.TestCase):
 
     for test_input in test_inputs:
       self.assertRaises(ValueError, get_config_policy, test_input)
+
+  def test_pickleability(self):
+    """
+    Checks that we can unpickle ExitPolicy instances.
+    """
+
+    policy = ExitPolicy("accept *:80", "accept *:443", "reject *:*")
+    self.assertTrue(policy.can_exit_to('74.125.28.106', 80))
+
+    encoded_policy = pickle.dumps(policy)
+    restored_policy = pickle.loads(encoded_policy)
+
+    self.assertEqual(policy, restored_policy)
+    self.assertTrue(restored_policy.is_exiting_allowed())
+    self.assertTrue(restored_policy.can_exit_to('74.125.28.106', 80))



More information about the tor-commits mailing list