Repository: mathesar-foundation/mathesar

Issue:
Error "cursor already closed"
I've been getting this error from the various endpoints occasionally:

> InterfaceError at /api/db/v0/tables/19/columns/
>
> (psycopg2.InterfaceError) cursor already closed
> (Background on this error at: http://sqlalche.me/e/14/rvf5)

![image](https://user-images.githubusercontent.com/42411/185951883-8023486a-b47b-402c-9a0b-48d0a1027ef2.png)

([Full error](https://rawgit.com/seancolsen/f9d5a5d856957272583e9e5af1a7e78c/raw/error-cursor-already-closed.html))

I can't reproduce it but it seems to happen when I'm submitting multiple API requests in parallel.



Discussion:
This reminds me of [this discussion on Hacker News](https://news.ycombinator.com/item?id=32495846) that I saw yesterday full of people complaining about the pitfalls of cursor based pagination.
@seancolsen  is this still happening? We've changed the relevant section of the code, including some things that would change how cursors are managed, since you submitted this.
@mathemancer Yep. I'm able to reproduce this pretty consistently with the following steps:

1. Open the Data Explorer
1. Select Checkouts as the base table.
1. Expand "Item". Expand "Publication" within it.
1. Expand "Patron".
1. Quickly click on all 17 possible columns, adding them each to the query in sequence.
1. Observe 17 POST requests to `http://localhost:8000/api/db/v0/queries/run/`, the last of which resolves with a 500 HTTP status and the "cursor already closed" error described above.

@seancolsen I'll look into it, thanks.
Update: At some point, I _was_ able to reproduce this. However, I can't seem to make it happen at all on the current `master` branch.
- I've tried the method described by @seancolsen , as well as an easier one (just spam adding the same column over and over in Data Explorer).
- I've checked my local installation as well as staging.

Edit: Welp. I had moved on to a different bug, and this one jumped up and bit me. Ignore my previous success message.

Resolving patch:
diff --git a/config/settings.py b/config/settings.py
--- a/config/settings.py
+++ b/config/settings.py
@@ -52,6 +52,7 @@ def pipe_delim(pipe_string):
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    "mathesar.middleware.CursorClosedHandlerMiddleware",
 ]
 
 ROOT_URLCONF = "config.urls"
diff --git a/mathesar/middleware.py b/mathesar/middleware.py
new file mode 100644
--- /dev/null
+++ b/mathesar/middleware.py
@@ -0,0 +1,20 @@
+import time
+import warnings
+
+from sqlalchemy.exc import InterfaceError
+
+
+class CursorClosedHandlerMiddleware:
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        response = self.get_response(request)
+        return response
+
+    def process_exception(self, request, exception):
+        if isinstance(exception, InterfaceError):
+            warnings.warn("InterfaceError caught; trying again.")
+            time.sleep(1)
+            response = self.get_response(request)
+            return response


Test patch: