|
| 1 | +import logging |
1 | 2 | from pathlib import Path |
2 | 3 | from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union |
3 | 4 |
|
4 | 5 | import rdflib |
5 | | -from ontoenv import OntoEnv |
| 6 | +from ontoenv import CatalogRecoveryError, OntoEnv |
6 | 7 |
|
7 | 8 | from buildingmotif.database.graph_connection import _is_uuid |
8 | 9 |
|
@@ -57,7 +58,50 @@ def __init__( |
57 | 58 | # what init_from_store did at construction time |
58 | 59 | self.env.refresh_from_store(full=True) |
59 | 60 | else: |
60 | | - self.env = OntoEnv.connect(str(path), graph_store=store, **options) |
| 61 | + self.env = self._connect_recovering(str(path), store, options) |
| 62 | + |
| 63 | + @staticmethod |
| 64 | + def _connect_recovering( |
| 65 | + path: str, store: Optional["BuildingMOTIFGraphStore"], options: Dict[str, Any] |
| 66 | + ) -> OntoEnv: |
| 67 | + """``OntoEnv.connect``, clearing a stale interrupted-mutation marker. |
| 68 | +
|
| 69 | + **This is a workaround for an ontoenv defect; delete it once ontoenv |
| 70 | + stops leaving the marker for tolerable failures.** |
| 71 | +
|
| 72 | + ontoenv writes ``.ontoenv/catalog.pending`` when it begins a batched |
| 73 | + mutation and removes it only when the batch finishes cleanly |
| 74 | + (``BatchScope::run``). An *unresolvable* ``owl:imports`` makes the batch |
| 75 | + end in error, so the marker survives -- and every later open of that |
| 76 | + cache raises ``CatalogRecoveryError``. |
| 77 | +
|
| 78 | + That is routine here rather than exceptional: Brick declares eight |
| 79 | + imports that do not resolve offline (several 404 even online), so a |
| 80 | + single ``Library.from_ontology(Brick)`` permanently poisons a |
| 81 | + persistent cache. An ontology with no imports leaves no marker, which |
| 82 | + is how this was isolated. ontoenv exposes no recovery call, so the only |
| 83 | + way through is to remove the file. |
| 84 | +
|
| 85 | + The cost of doing so: after a genuine crash mid-write the marker is a |
| 86 | + real signal, and this clears it. That is the trade being made -- one |
| 87 | + warned retry, and if the retry also fails the error propagates. |
| 88 | + """ |
| 89 | + try: |
| 90 | + return OntoEnv.connect(path, graph_store=store, **options) |
| 91 | + except CatalogRecoveryError: |
| 92 | + marker = Path(path) / ".ontoenv" / "catalog.pending" |
| 93 | + if not marker.exists(): |
| 94 | + raise |
| 95 | + logging.getLogger(__name__).warning( |
| 96 | + "Clearing stale ontoenv recovery marker at %s. This is normally " |
| 97 | + "left behind by an unresolvable owl:imports rather than by an " |
| 98 | + "actual interrupted write; if this repeats, the ontology cache " |
| 99 | + "at %s may genuinely be damaged and can be deleted.", |
| 100 | + marker, |
| 101 | + path, |
| 102 | + ) |
| 103 | + marker.unlink() |
| 104 | + return OntoEnv.connect(path, graph_store=store, **options) |
61 | 105 |
|
62 | 106 | def close(self) -> None: |
63 | 107 | self.env.close() |
|
0 commit comments