WIP: fix yet another SQLite concurrency bug
diff --git a/HISTORY.md b/HISTORY.md
index cb165b1..c706748 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,9 @@
 # History
 
+## Unreleased
+* Fix intermittent data corruption (and resulting `EOFError`) in the SQLite backend under
+  concurrent thread access
+
 ## 1.3.3 (2026-07-03)
 * Fix SQLite `vacuum()` not freeing disk space
 * Fix DynamoDB item enumeration when the table exceeds 1MB
diff --git a/requests_cache/backends/base.py b/requests_cache/backends/base.py
index e980ae7..a74a3bb 100644
--- a/requests_cache/backends/base.py
+++ b/requests_cache/backends/base.py
@@ -25,7 +25,7 @@
 from ..serializers import SerializerType, init_serializer
 
 # Specific exceptions that may be raised during deserialization
-DESERIALIZE_ERRORS = (AttributeError, ImportError, PickleError, TypeError, ValueError)
+DESERIALIZE_ERRORS = (EOFError, AttributeError, ImportError, PickleError, TypeError, ValueError)
 
 logger = getLogger(__name__)
 
diff --git a/requests_cache/backends/sqlite.py b/requests_cache/backends/sqlite.py
index 5b6d7ee..fa3d7a5 100644
--- a/requests_cache/backends/sqlite.py
+++ b/requests_cache/backends/sqlite.py
@@ -246,7 +246,14 @@
 
     @contextmanager
     def connection(self, commit=False) -> Iterator[sqlite3.Connection]:
-        """Get a thread-local database connection"""
+        """Get a synchronized database connection.
+
+        A single connection object is shared across all threads for this cache (see
+        ``check_same_thread=False`` above), so all access to it -- reads included -- must be
+        serialized under ``self._lock`` for the full duration it's in use, not just while it's
+        being lazily opened. Otherwise a read could run concurrently with an in-progress write
+        transaction on the same connection object, corrupting results.
+        """
         with self._lock:
             if not self._connection:
                 logger.debug(f'Opening connection to {self.db_path}:{self.table_name}')
@@ -264,13 +271,13 @@
                 if self.wal and not self.fast_save:
                     self._connection.execute('PRAGMA synchronous=NORMAL')
 
-        # Multithreaded write operations must be run in serial
-        if commit and not self._active_transaction:
-            with self._acquire_sqlite_lock():
+            # Multithreaded write operations must be run in serial
+            if commit and not self._active_transaction:
+                with self._acquire_sqlite_lock():
+                    yield self._connection
+            # Reads are also serialized (behind the same lock), since the connection is shared
+            else:
                 yield self._connection
-        # Read operations can be run in parallel (no lock or COMMIT)
-        else:
-            yield self._connection
 
     def close(self):
         """Close any active connections"""