Skip to content

GeoIP cache refresh fails on Windows, causing repeated 70 MB downloads on every launch #458

Description

@tienanhemho

Summary

After the cached GeoIP database becomes older than 30 days, every GeoIP-enabled browser launch downloads the approximately 70 MB database again.

The download does not successfully replace the existing database on Windows, so the cache remains stale and the same download is triggered indefinitely.

Launching many profiles can therefore cause many repeated or concurrent downloads of the same shared file.

Root cause

The issue is caused by the combination of two problems in geoip.py.

1. The existing database cannot be replaced on Windows

When the database is older than GEOIP_UPDATE_INTERVAL, _maybe_trigger_update() starts a background download:

_download_geoip_db(db_path)

After downloading, _download_geoip_db() attempts to update the existing database with:

tmp_path.rename(dest)

On Windows, Path.rename() cannot overwrite an existing destination file. It raises FileExistsError/WinError 183 when GeoLite2-City.mmdb already exists.

The background thread catches this exception and logs it only at debug level:

except Exception:
    logger.debug("Background GeoIP update failed", exc_info=True)

As a result:

  1. The full database is downloaded.
  2. Replacing the existing database fails.
  3. The temporary download is deleted.
  4. The old database modification time remains unchanged.
  5. The database is still considered older than 30 days.
  6. The next browser launch starts another full download.

This creates an endless download loop after the cache first expires.

2. There is no update-in-progress guard

Every call to _maybe_trigger_update() creates a new thread when the database is stale:

threading.Thread(target=_bg, daemon=True).start()

There is no lock or shared "update in progress" state.

If many profiles are launched while the cache is stale, every launch can start another approximately 70 MB download.

The initial download has a similar race: multiple callers can all see that the database does not exist and begin downloading it simultaneously.

Environment

  • OS: Windows
  • CloakBrowser tested: 0.3.31
  • The same affected logic appears to still be present in 0.4.12
  • Cache file: ~/.cloakbrowser/geoip/GeoLite2-City.mmdb

Steps to reproduce

  1. Enable geoip=True and configure a proxy.
  2. Ensure GeoLite2-City.mmdb exists and is older than 30 days.
  3. Launch a browser profile.
  4. Observe the GeoIP database download.
  5. After the download finishes, check the database modification time.
  6. Launch the profile again.

The database modification time remains unchanged and the complete download starts again.

Example logs:

cloakbrowser INFO GeoIP download: 79 %
cloakbrowser INFO GeoIP download: 89 %
cloakbrowser INFO GeoIP download: 99 %

When multiple profiles are launched, multiple copies of these download sequences can appear.

Expected behavior

  • Only one refresh should run when the database becomes stale.
  • Other launches should continue using the existing database.
  • The downloaded database should atomically replace the existing file on Windows.
  • Concurrent launches should not start duplicate downloads.

Suggested fix

Use os.replace() or Path.replace() instead of Path.rename():

tmp_path.replace(dest)

Also add a process-wide lock or update state so only one initial download or background refresh can run at a time.

For stale databases, the existing database should remain usable while one background refresh is running. If the refresh fails, the existing database should be retained and retries should be rate-limited.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions