Repository: cloud-custodian/cloud-custodian

Issue:
sqlkvcache errors when using `value_from`
### Describe the bug

_Discussion salvaged from closed PR #7692_

When using the sqlite3-based cache, I'm hitting errors running policies that contain a `value_from` lookup. This may point to additional issues with cache connections.

### What did you expect to happen?

Cache lookups to work if `value_from` is in play.

### Cloud Provider

Amazon Web Services (AWS)

### Cloud Custodian version and dependency information

```shell
develop build using commit a88b467
```


### Policy

```shell
policies:
  - name: sqs-metrics
    resource: aws.sqs
    filters:
      - type: value
        key: tag:Owner
        op: in
        value_from:
            url: s3://my_bucket/valid_tags.json
            format: json
            expr: 'Owner[]'
```


### Relevant log/traceback output

```shell
Traceback (most recent call last):
  File "/home/aj/code/cloud-custodian/c7n/commands.py", line 302, in run
    policy()
  File "/home/aj/code/cloud-custodian/c7n/policy.py", line 1281, in __call__
    resources = PullMode(self).run()
  File "/home/aj/code/cloud-custodian/c7n/policy.py", line 294, in run
    resources = self.policy.resource_manager.resources()
  File "/home/aj/code/cloud-custodian/c7n/query.py", line 535, in resources
    resources = self.filter_resources(resources)
  File "/home/aj/code/cloud-custodian/c7n/manager.py", line 111, in filter_resources
    resources = f.process(resources, event)
  File "/home/aj/code/cloud-custodian/c7n/filters/core.py", line 553, in process
    return super(ValueFilter, self).process(resources, event)
  File "/home/aj/code/cloud-custodian/c7n/filters/core.py", line 190, in process
    return list(filter(self, resources))
  File "/home/aj/code/cloud-custodian/c7n/filters/core.py", line 540, in __call__
    matched = self.match(i)
  File "/home/aj/code/cloud-custodian/c7n/filters/core.py", line 566, in match
    self.v = values.get_values()
  File "/home/aj/code/cloud-custodian/c7n/resolver.py", line 157, in get_values
    self.cache.save(("value-from", key), contents)
  File "/home/aj/code/cloud-custodian/c7n/cache.py", line 158, in save
    with self.conn as cursor:
AttributeError: __enter__
```


### Extra information or context

Prior to the fix in a88b467395702173fbdd35f018b716879aa93a1a this policy was dying [on the get](https://github.com/cloud-custodian/cloud-custodian/blob/a88b467395702173fbdd35f018b716879aa93a1a/c7n/resolver.py#L151) and afterward it started dying [on the save](https://github.com/cloud-custodian/cloud-custodian/blob/a88b467395702173fbdd35f018b716879aa93a1a/c7n/resolver.py#L157). Looked like it was because in between the cache get and cache save, we call [contents = self._get_values()](https://github.com/cloud-custodian/cloud-custodian/blob/a88b467395702173fbdd35f018b716879aa93a1a/c7n/resolver.py#L155) which goes get_contents() --> resolve() which closes the connection and sets it to None.


Discussion:

Resolving patch:
diff --git a/c7n/query.py b/c7n/query.py
--- a/c7n/query.py
+++ b/c7n/query.py
@@ -509,26 +509,23 @@ def resources(self, query=None, augment=True) -> List[dict]:
         cache_key = self.get_cache_key(query)
         resources = None
 
-        if self._cache.load():
+        with self._cache:
             resources = self._cache.get(cache_key)
             if resources is not None:
                 self.log.debug("Using cached %s: %d" % (
-                    "%s.%s" % (self.__class__.__module__,
-                               self.__class__.__name__),
+                    "%s.%s" % (self.__class__.__module__, self.__class__.__name__),
                     len(resources)))
 
-        if resources is None:
-            if query is None:
-                query = {}
-            with self.ctx.tracer.subsegment('resource-fetch'):
-                resources = self.source.resources(query)
-            if augment:
-                with self.ctx.tracer.subsegment('resource-augment'):
-                    resources = self.augment(resources)
-                # Don't pollute cache with unaugmented resources.
-                self._cache.save(cache_key, resources)
-
-        self._cache.close()
+            if resources is None:
+                if query is None:
+                    query = {}
+                with self.ctx.tracer.subsegment('resource-fetch'):
+                    resources = self.source.resources(query)
+                if augment:
+                    with self.ctx.tracer.subsegment('resource-augment'):
+                        resources = self.augment(resources)
+                    # Don't pollute cache with unaugmented resources.
+                    self._cache.save(cache_key, resources)
 
         resource_count = len(resources)
         with self.ctx.tracer.subsegment('filter'):
@@ -551,14 +548,13 @@ def check_resource_limit(self, selection_count, population_count):
 
     def _get_cached_resources(self, ids):
         key = self.get_cache_key(None)
-        if self._cache.load():
+        with self._cache:
             resources = self._cache.get(key)
             if resources is not None:
                 self.log.debug("Using cached results for get_resources")
                 m = self.get_model()
                 id_set = set(ids)
                 return [r for r in resources if r[m.id] in id_set]
-        self._cache.close()
         return None
 
     def get_resources(self, ids, cache=True, augment=True):
diff --git a/c7n/resolver.py b/c7n/resolver.py
--- a/c7n/resolver.py
+++ b/c7n/resolver.py
@@ -12,6 +12,7 @@
 import zlib
 from contextlib import closing
 
+from c7n.cache import NullCache
 from c7n.utils import format_string_values
 
 log = logging.getLogger('custodian.resolver')
@@ -26,11 +27,9 @@ def __init__(self, session_factory, cache):
         self.cache = cache
 
     def resolve(self, uri):
-        if self.cache and self.cache.load():
-            contents = self.cache.get(("uri-resolver", uri))
-            if contents is not None:
-                self.cache.close()
-                return contents
+        contents = self.cache.get(("uri-resolver", uri))
+        if contents is not None:
+            return contents
 
         if uri.startswith('s3://'):
             contents = self.get_s3_uri(uri)
@@ -39,9 +38,7 @@ def resolve(self, uri):
             with closing(urlopen(req)) as response:  # nosec nosemgrep
                 contents = self.handle_response_encoding(response)
 
-        if self.cache:
-            self.cache.save(("uri-resolver", uri), contents)
-            self.cache.close()
+        self.cache.save(("uri-resolver", uri), contents)
         return contents
 
     def handle_response_encoding(self, response):
@@ -124,8 +121,8 @@ def __init__(self, data, manager):
         }
         self.data = format_string_values(data, **config_args)
         self.manager = manager
-        self.cache = manager._cache
-        self.resolver = URIResolver(manager.session_factory, manager._cache)
+        self.cache = manager._cache or NullCache({})
+        self.resolver = URIResolver(manager.session_factory, self.cache)
 
     def get_contents(self):
         _, format = os.path.splitext(self.data['url'])
@@ -144,19 +141,16 @@ def get_contents(self):
 
     def get_values(self):
         key = [self.data.get(i) for i in ('url', 'format', 'expr')]
-        if self.cache and self.cache.load():
+        with self.cache:
             # use these values as a key to cache the result so if we have
             # the same filter happening across many resources, we can reuse
             # the results.
             contents = self.cache.get(("value-from", key))
             if contents is not None:
                 return contents
-
-        contents = self._get_values()
-        if self.cache:
+            contents = self._get_values()
             self.cache.save(("value-from", key), contents)
-            self.cache.close()
-        return contents
+            return contents
 
     def _get_values(self):
         contents, format = self.get_contents()


Test patch:
diff --git a/tests/test_resolver.py b/tests/test_resolver.py
--- a/tests/test_resolver.py
+++ b/tests/test_resolver.py
@@ -37,6 +37,12 @@ def load(self):
     def close(self):
         pass
 
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *args, **kw):
+        return
+
 
 class FakeResolver: