Use dummy fallback engines if imports fail (#12979)

pull/12984/head
David Robertson 2022-06-07 17:33:55 +01:00 committed by GitHub
parent a10cc5f824
commit 586bfc6dc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 21 deletions

1
changelog.d/12979.bugfix Normal file
View File

@ -0,0 +1 @@
Fix a bug introduced in Synapse 1.60 where Synapse would fail to start if the `sqlite3` module was not available.

View File

@ -46,7 +46,7 @@ from synapse.storage.database import (
) )
from synapse.storage.databases.main.events_worker import EventCacheEntry from synapse.storage.databases.main.events_worker import EventCacheEntry
from synapse.storage.databases.main.search import SearchEntry from synapse.storage.databases.main.search import SearchEntry
from synapse.storage.engines.postgres import PostgresEngine from synapse.storage.engines import PostgresEngine
from synapse.storage.util.id_generators import AbstractStreamIdGenerator from synapse.storage.util.id_generators import AbstractStreamIdGenerator
from synapse.storage.util.sequence import SequenceGenerator from synapse.storage.util.sequence import SequenceGenerator
from synapse.types import JsonDict, StateMap, get_domain_from_id from synapse.types import JsonDict, StateMap, get_domain_from_id

View File

@ -11,11 +11,35 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from typing import Any, Mapping from typing import Any, Mapping, NoReturn
from ._base import BaseDatabaseEngine, IncorrectDatabaseSetup from ._base import BaseDatabaseEngine, IncorrectDatabaseSetup
# The classes `PostgresEngine` and `Sqlite3Engine` must always be importable, because
# we use `isinstance(engine, PostgresEngine)` to write different queries for postgres
# and sqlite. But the database driver modules are both optional: they may not be
# installed. To account for this, create dummy classes on import failure so we can
# still run `isinstance()` checks.
try:
from .postgres import PostgresEngine from .postgres import PostgresEngine
except ImportError:
class PostgresEngine(BaseDatabaseEngine): # type: ignore[no-redef]
def __new__(cls, *args: object, **kwargs: object) -> NoReturn: # type: ignore[misc]
raise RuntimeError(
f"Cannot create {cls.__name__} -- psycopg2 module is not installed"
)
try:
from .sqlite import Sqlite3Engine from .sqlite import Sqlite3Engine
except ImportError:
class Sqlite3Engine(BaseDatabaseEngine): # type: ignore[no-redef]
def __new__(cls, *args: object, **kwargs: object) -> NoReturn: # type: ignore[misc]
raise RuntimeError(
f"Cannot create {cls.__name__} -- sqlite3 module is not installed"
)
def create_engine(database_config: Mapping[str, Any]) -> BaseDatabaseEngine: def create_engine(database_config: Mapping[str, Any]) -> BaseDatabaseEngine:
@ -30,4 +54,10 @@ def create_engine(database_config: Mapping[str, Any]) -> BaseDatabaseEngine:
raise RuntimeError("Unsupported database engine '%s'" % (name,)) raise RuntimeError("Unsupported database engine '%s'" % (name,))
__all__ = ["create_engine", "BaseDatabaseEngine", "IncorrectDatabaseSetup"] __all__ = [
"create_engine",
"BaseDatabaseEngine",
"PostgresEngine",
"Sqlite3Engine",
"IncorrectDatabaseSetup",
]

View File

@ -15,6 +15,8 @@
import logging import logging
from typing import TYPE_CHECKING, Any, Mapping, NoReturn, Optional, Tuple, cast from typing import TYPE_CHECKING, Any, Mapping, NoReturn, Optional, Tuple, cast
import psycopg2.extensions
from synapse.storage.engines._base import ( from synapse.storage.engines._base import (
BaseDatabaseEngine, BaseDatabaseEngine,
IncorrectDatabaseSetup, IncorrectDatabaseSetup,
@ -23,18 +25,14 @@ from synapse.storage.engines._base import (
from synapse.storage.types import Cursor from synapse.storage.types import Cursor
if TYPE_CHECKING: if TYPE_CHECKING:
import psycopg2 # noqa: F401
from synapse.storage.database import LoggingDatabaseConnection from synapse.storage.database import LoggingDatabaseConnection
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PostgresEngine(BaseDatabaseEngine["psycopg2.connection"]): class PostgresEngine(BaseDatabaseEngine[psycopg2.extensions.connection]):
def __init__(self, database_config: Mapping[str, Any]): def __init__(self, database_config: Mapping[str, Any]):
import psycopg2.extensions
super().__init__(psycopg2, database_config) super().__init__(psycopg2, database_config)
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
@ -69,7 +67,9 @@ class PostgresEngine(BaseDatabaseEngine["psycopg2.connection"]):
return collation, ctype return collation, ctype
def check_database( def check_database(
self, db_conn: "psycopg2.connection", allow_outdated_version: bool = False self,
db_conn: psycopg2.extensions.connection,
allow_outdated_version: bool = False,
) -> None: ) -> None:
# Get the version of PostgreSQL that we're using. As per the psycopg2 # Get the version of PostgreSQL that we're using. As per the psycopg2
# docs: The number is formed by converting the major, minor, and # docs: The number is formed by converting the major, minor, and
@ -176,8 +176,6 @@ class PostgresEngine(BaseDatabaseEngine["psycopg2.connection"]):
return True return True
def is_deadlock(self, error: Exception) -> bool: def is_deadlock(self, error: Exception) -> bool:
import psycopg2.extensions
if isinstance(error, psycopg2.DatabaseError): if isinstance(error, psycopg2.DatabaseError):
# https://www.postgresql.org/docs/current/static/errcodes-appendix.html # https://www.postgresql.org/docs/current/static/errcodes-appendix.html
# "40001" serialization_failure # "40001" serialization_failure
@ -185,7 +183,7 @@ class PostgresEngine(BaseDatabaseEngine["psycopg2.connection"]):
return error.pgcode in ["40001", "40P01"] return error.pgcode in ["40001", "40P01"]
return False return False
def is_connection_closed(self, conn: "psycopg2.connection") -> bool: def is_connection_closed(self, conn: psycopg2.extensions.connection) -> bool:
return bool(conn.closed) return bool(conn.closed)
def lock_table(self, txn: Cursor, table: str) -> None: def lock_table(self, txn: Cursor, table: str) -> None:
@ -205,18 +203,16 @@ class PostgresEngine(BaseDatabaseEngine["psycopg2.connection"]):
else: else:
return "%i.%i.%i" % (numver / 10000, (numver % 10000) / 100, numver % 100) return "%i.%i.%i" % (numver / 10000, (numver % 10000) / 100, numver % 100)
def in_transaction(self, conn: "psycopg2.connection") -> bool: def in_transaction(self, conn: psycopg2.extensions.connection) -> bool:
import psycopg2.extensions
return conn.status != psycopg2.extensions.STATUS_READY return conn.status != psycopg2.extensions.STATUS_READY
def attempt_to_set_autocommit( def attempt_to_set_autocommit(
self, conn: "psycopg2.connection", autocommit: bool self, conn: psycopg2.extensions.connection, autocommit: bool
) -> None: ) -> None:
return conn.set_session(autocommit=autocommit) return conn.set_session(autocommit=autocommit)
def attempt_to_set_isolation_level( def attempt_to_set_isolation_level(
self, conn: "psycopg2.connection", isolation_level: Optional[int] self, conn: psycopg2.extensions.connection, isolation_level: Optional[int]
) -> None: ) -> None:
if isolation_level is None: if isolation_level is None:
isolation_level = self.default_isolation_level isolation_level = self.default_isolation_level

View File

@ -23,8 +23,7 @@ from typing_extensions import Counter as CounterType
from synapse.config.homeserver import HomeServerConfig from synapse.config.homeserver import HomeServerConfig
from synapse.storage.database import LoggingDatabaseConnection from synapse.storage.database import LoggingDatabaseConnection
from synapse.storage.engines import BaseDatabaseEngine from synapse.storage.engines import BaseDatabaseEngine, PostgresEngine
from synapse.storage.engines.postgres import PostgresEngine
from synapse.storage.schema import SCHEMA_COMPAT_VERSION, SCHEMA_VERSION from synapse.storage.schema import SCHEMA_COMPAT_VERSION, SCHEMA_VERSION
from synapse.storage.types import Cursor from synapse.storage.types import Cursor