Skip to content
Closed
11 changes: 5 additions & 6 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
- name: Set up uv
uses: astral-sh/setup-uv@v3
with:
python-version: '3.12'
cache: 'pip'
python-version: "3.13"

- name: Install dependencies
run: pip install httpx python-dotenv
run: uv sync --frozen

- name: Run sync script
env:
TOKEN: ${{ secrets.TOKEN }}
PROFILE: ${{ secrets.PROFILE }}
run: python main.py
run: uv run python main.py
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ The included GitHub Actions workflow (`.github/workflows/ci.yml`) runs a dry-run
- `PROFILE`: your Control D profile ID(s)

## Requirements
- Python 3.12+
- Python 3.13+
- `uv` (for dependency management)
28 changes: 23 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import os
import logging
import time
import concurrent.futures
import re
from typing import Dict, List, Optional, Any, Set, Sequence

Expand Down Expand Up @@ -350,14 +351,31 @@ def sync_profile(
try:
# Fetch all folder data first
folder_data_list = []
for url in folder_urls:
if not validate_folder_url(url):
continue

# Validate URLs first
valid_urls = [url for url in folder_urls if validate_folder_url(url)]

invalid_count = len(folder_urls) - len(valid_urls)
if invalid_count > 0:
log.warning(f"Filtered out {invalid_count} invalid URL(s)")

if not valid_urls:
log.error("No valid folder URLs to fetch")
return False

def safe_fetch(url):
try:
folder_data_list.append(fetch_folder_data(url))
return fetch_folder_data(url)
except (httpx.HTTPError, KeyError) as e:
log.error(f"Failed to fetch folder data from {url}: {e}")
continue
return None
Comment on lines +366 to +371
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The safe_fetch function is accessing shared mutable state (_cache dictionary) from multiple threads without synchronization. The _gh_get function called by fetch_folder_data performs a check-then-act operation on the cache that is not thread-safe. Multiple threads could simultaneously check if a URL is in the cache, both find it's missing, and then both attempt to fetch and store it, potentially causing race conditions or inconsistent state. Consider adding a lock around the cache access in _gh_get or using a thread-safe caching mechanism like functools.lru_cache with appropriate thread safety guarantees.

Copilot uses AI. Check for mistakes.
Comment on lines +366 to +371
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global _gh httpx.Client instance (line 92) is being accessed concurrently from multiple threads without consideration for thread safety. While httpx.Client connections can be reused, concurrent access to the same client instance from multiple threads can lead to connection pool contention and potential race conditions. Consider creating separate httpx.Client instances per thread, or verify that the httpx version being used provides thread-safe client instances.

Copilot uses AI. Check for mistakes.

# Fetch folder data in parallel to speed up startup
max_workers = min(10, len(valid_urls))
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
results = executor.map(safe_fetch, valid_urls)

folder_data_list = [r for r in results if r is not None]

if not folder_data_list:
log.error("No valid folder data found")
Expand Down
Loading