mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-01 04:13:46 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d68a6cd87d |
@@ -41,12 +41,7 @@ def _compute_hash(file_path: str) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def verify_file(file_path: str, expected_hash: str) -> bool:
|
def verify_file(file_path: str, expected_hash: str) -> bool:
|
||||||
file_hash = _compute_hash(file_path)
|
|
||||||
return file_hash == expected_hash.lower() if file_hash else False
|
|
||||||
|
|
||||||
|
|
||||||
def _verify_file(file_path: str, expected_hash: str) -> bool:
|
|
||||||
file_hash = _compute_hash(file_path)
|
file_hash = _compute_hash(file_path)
|
||||||
return file_hash == expected_hash.lower() if file_hash else False
|
return file_hash == expected_hash.lower() if file_hash else False
|
||||||
|
|
||||||
@@ -81,7 +76,7 @@ def _bundle_artifacts(bundle: custom.ModelManagerSP.ModelBundle) -> list[tuple[s
|
|||||||
|
|
||||||
def _bundle_is_valid_locally(bundle: custom.ModelManagerSP.ModelBundle) -> bool:
|
def _bundle_is_valid_locally(bundle: custom.ModelManagerSP.ModelBundle) -> bool:
|
||||||
model_root = Paths.model_root()
|
model_root = Paths.model_root()
|
||||||
return all(_verify_file(os.path.join(model_root, file_name), expected_hash)
|
return all(verify_file(os.path.join(model_root, file_name), expected_hash)
|
||||||
for file_name, expected_hash in _bundle_artifacts(bundle))
|
for file_name, expected_hash in _bundle_artifacts(bundle))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from openpilot.common.file_chunker import get_chunk_name
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.realtime import Ratekeeper
|
from openpilot.common.realtime import Ratekeeper
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
@@ -22,6 +23,8 @@ from openpilot.sunnypilot.models.helpers import (ACTIVE_BUNDLE_KEYS, get_active_
|
|||||||
|
|
||||||
# (connect, read) seconds. read is per-request inactivity, not a total cap
|
# (connect, read) seconds. read is per-request inactivity, not a total cap
|
||||||
DOWNLOAD_TIMEOUT = (30, 30)
|
DOWNLOAD_TIMEOUT = (30, 30)
|
||||||
|
# how many download+verify rounds before giving up on a chunk that won't verify
|
||||||
|
MAX_CHUNK_VERIFY_ATTEMPTS = 3
|
||||||
|
|
||||||
|
|
||||||
class DownloadCancelled(Exception):
|
class DownloadCancelled(Exception):
|
||||||
@@ -153,6 +156,35 @@ class ModelManagerSP:
|
|||||||
os.remove(base_path)
|
os.remove(base_path)
|
||||||
del self._download_start_times[artifact.fileName]
|
del self._download_start_times[artifact.fileName]
|
||||||
|
|
||||||
|
async def _verify_chunks_parallel(self, artifact, full_path: str) -> set[int]:
|
||||||
|
num_chunks = len(artifact.chunks)
|
||||||
|
start = time.monotonic()
|
||||||
|
futures = []
|
||||||
|
chunk_index: dict[asyncio.Future, int] = {}
|
||||||
|
for i, chunk in enumerate(artifact.chunks):
|
||||||
|
fut = asyncio.ensure_future(asyncio.to_thread(verify_file, get_chunk_name(full_path, i, num_chunks), chunk.sha256))
|
||||||
|
futures.append(fut)
|
||||||
|
chunk_index[fut] = i
|
||||||
|
|
||||||
|
valid_chunks: set[int] = set()
|
||||||
|
pending = set(futures)
|
||||||
|
while pending:
|
||||||
|
if self._download_interrupted():
|
||||||
|
for t in pending:
|
||||||
|
t.cancel()
|
||||||
|
raise DownloadCancelled("Download cancelled")
|
||||||
|
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
||||||
|
for fut in done:
|
||||||
|
if fut.result():
|
||||||
|
valid_chunks.add(chunk_index[fut])
|
||||||
|
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.verifying
|
||||||
|
artifact.downloadProgress.progress = (len(valid_chunks) / num_chunks) * 100
|
||||||
|
self._sync_artifact_progress(artifact)
|
||||||
|
self._report_status()
|
||||||
|
elapsed = time.monotonic() - start
|
||||||
|
cloudlog.info(f"Verified {len(valid_chunks)}/{num_chunks} chunks of {artifact.fileName} in {elapsed:.2f}s")
|
||||||
|
return valid_chunks
|
||||||
|
|
||||||
async def _process_artifact(self, artifact, destination_path: str) -> None:
|
async def _process_artifact(self, artifact, destination_path: str) -> None:
|
||||||
if not artifact.downloadUri.uri:
|
if not artifact.downloadUri.uri:
|
||||||
return None
|
return None
|
||||||
@@ -170,20 +202,10 @@ class ModelManagerSP:
|
|||||||
is_cached = False
|
is_cached = False
|
||||||
valid_chunks: set[int] = set()
|
valid_chunks: set[int] = set()
|
||||||
if len(artifact.chunks) > 0:
|
if len(artifact.chunks) > 0:
|
||||||
from openpilot.common.file_chunker import get_chunk_name
|
valid_chunks = await self._verify_chunks_parallel(artifact, full_path)
|
||||||
num_chunks = len(artifact.chunks)
|
is_cached = len(valid_chunks) == len(artifact.chunks)
|
||||||
for i, chunk in enumerate(artifact.chunks):
|
|
||||||
if self._download_interrupted():
|
|
||||||
raise DownloadCancelled("Download cancelled")
|
|
||||||
if await verify_file(get_chunk_name(full_path, i, num_chunks), chunk.sha256):
|
|
||||||
valid_chunks.add(i)
|
|
||||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.verifying
|
|
||||||
artifact.downloadProgress.progress = (len(valid_chunks) / num_chunks) * 100
|
|
||||||
self._sync_artifact_progress(artifact)
|
|
||||||
self._report_status()
|
|
||||||
is_cached = len(valid_chunks) == num_chunks
|
|
||||||
else:
|
else:
|
||||||
if await verify_file(full_path, expected_hash):
|
if verify_file(full_path, expected_hash):
|
||||||
is_cached = True
|
is_cached = True
|
||||||
|
|
||||||
if is_cached:
|
if is_cached:
|
||||||
@@ -195,15 +217,18 @@ class ModelManagerSP:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if len(artifact.chunks) > 0:
|
if len(artifact.chunks) > 0:
|
||||||
await self._download_chunked(url, full_path, artifact, skip=valid_chunks)
|
attempts = 0
|
||||||
from openpilot.common.file_chunker import get_chunk_name
|
while len(valid_chunks) < len(artifact.chunks):
|
||||||
for i, chunk in enumerate(artifact.chunks):
|
attempts += 1
|
||||||
chunk_path = get_chunk_name(full_path, i, len(artifact.chunks))
|
cloudlog.warning(f"Re-downloading {len(artifact.chunks) - len(valid_chunks)} invalid chunk(s) of {filename} (attempt {attempts})")
|
||||||
if not await verify_file(chunk_path, chunk.sha256):
|
await self._download_chunked(url, full_path, artifact, skip=valid_chunks)
|
||||||
raise ValueError(f"Hash validation failed for chunk {i+1} of {filename}")
|
valid_chunks = await self._verify_chunks_parallel(artifact, full_path)
|
||||||
|
if len(valid_chunks) < len(artifact.chunks) and attempts >= MAX_CHUNK_VERIFY_ATTEMPTS:
|
||||||
|
missing = next(i for i in range(len(artifact.chunks)) if i not in valid_chunks)
|
||||||
|
raise ValueError(f"Hash validation failed for chunk {missing+1} of {filename} after {attempts} attempts")
|
||||||
else:
|
else:
|
||||||
await self._download_file(url, full_path, artifact)
|
await self._download_file(url, full_path, artifact)
|
||||||
if not await verify_file(full_path, expected_hash):
|
if not verify_file(full_path, expected_hash):
|
||||||
raise ValueError(f"Hash validation failed for {filename}")
|
raise ValueError(f"Hash validation failed for {filename}")
|
||||||
|
|
||||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||||
|
|||||||
Reference in New Issue
Block a user