|
9 | 9 | update_processed_heads, |
10 | 10 | ) |
11 | 11 | from crowdmail.enums import IntegrationResultState, IntegrationResultType, ListState |
| 12 | +from crowdmail.errors import CommandExecutionError |
12 | 13 | from crowdmail.logger import logger |
13 | 14 | from crowdmail.models.list import MailingList |
14 | 15 | from crowdmail.services.mirror.mirror_service import ( |
@@ -98,10 +99,25 @@ async def _process_single_list(mailing_list: MailingList): |
98 | 99 | shard = shard_index(shard_path) |
99 | 100 | commit_ids = await new_commits(shard_path, heads.get(shard)) |
100 | 101 | for git_id in commit_ids: |
| 102 | + # A git-read failure is operational/transient (disk hiccup, corrupt |
| 103 | + # mirror) — stop this shard without advancing past git_id so it's |
| 104 | + # retried next run, instead of checkpointing a message we never |
| 105 | + # actually read. A parse failure is content-level and permanent |
| 106 | + # (malformed message), so that case still advances the head below. |
| 107 | + try: |
| 108 | + message, blob_id = await asyncio.to_thread(read_email, shard_path, git_id) |
| 109 | + except CommandExecutionError as e: |
| 110 | + logger.error( |
| 111 | + "Stopping shard {} for retry after git-read failure on commit {}: {}", |
| 112 | + shard_path, |
| 113 | + git_id, |
| 114 | + repr(e), |
| 115 | + ) |
| 116 | + break |
| 117 | + |
101 | 118 | heads[shard] = git_id |
102 | 119 | dirty_heads[shard] = git_id |
103 | 120 | try: |
104 | | - message, blob_id = await asyncio.to_thread(read_email, shard_path, git_id) |
105 | 121 | parsed = parse_email( |
106 | 122 | message, |
107 | 123 | mailing_list.source_url, |
|
0 commit comments