Skip to content

Cap the total upload rate of a mirror - #15

Merged
v-positronic merged 6 commits into
mainfrom
upload-rate-cap
Sep 25, 2026
Merged

v-positronic merged 6 commits into
mainfrom
upload-rate-cap

Conversation

@v-positronic

@v-positronic v-positronic commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • pos3.mirror() and pos3.with_mirror() take max_upload_bytes_per_second. One limiter paces every upload in the context: all file workers, all multipart parts and all profiles together. max_workers and boto3's per-transfer concurrency do not multiply it. None (the default) uploads at full speed. Downloads are not limited. A rate that is not positive, NaN included, is refused. An infinite rate paces nothing.
  • The final sync on context exit waits for a background sync that is still running. The stop joins the worker for 60 s only, and a capped sync can run longer than that. Before, the two syncs then ran side by side. An error exit where no upload asks for a sync on error returns before the lock, so the caller's exception does not wait behind a background upload.
  • Version 0.4.0, with a CHANGELOG entry and a README entry.

How the cap works

boto3 calls upload_file's Callback in the thread that sends each part, after each read of the file and before those bytes go out. The callback now blocks in a shared ByteRateLimiter (pos3/rate_limit.py) until the bytes fit under the rate. Each call reserves the next slot on one timeline, so the bytes released since the first call never exceed the rate times the elapsed time. An idle limiter builds no credit.

boto3's own TransferConfig(max_bandwidth=...) limits one transfer manager. client.upload_file makes a new manager on each call, so that limit applies per file, and a shared manager still cannot span two profiles.

Tests

  • tests/test_rate_limit.py: the limiter on a fake clock (one shared timeline, no idle credit, a rewind is not paced, a zero or NaN rate is refused, an infinite rate never waits), and two end-to-end runs through real boto3 against a local S3 stub. Six files through eight workers at a 1 MiB/s cap take at least the time the cap allows. The test fails when the limiter is made per file. With no cap, the same path finishes at full speed.
  • tests/test_s3.py: the final sync does not overlap a background interval sync that outlives the join timeout. The test fails without the sync lock.
  • uv run pytest: 147 passed.

Tracked in Positronic-Robotics/internal#1637.

🤖 Generated with Claude Code

v-positronic and others added 4 commits September 24, 2026 09:41
A mirror can take `max_upload_bytes_per_second`. One shared limiter paces
every upload in the context: all file workers, all multipart parts and all
profiles together. Before, each boto3 transfer ran at full speed, so one
sync opened up to max_workers x 10 streams and filled the uplink.

`pos3.request_upload()` starts a background sync of every registered
upload within a second and returns at once. Each sync resets the interval
timer, so `interval` becomes a backstop for a caller that uploads at its
own moments.

The final sync on context exit now waits for a background sync that is
still running. The stop joins the worker for 60 s only, and a capped sync
can run longer than that.

Version 0.4.0.

Ticket: Positronic-Robotics/internal#1637 #refs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ticket: Positronic-Robotics/internal#1637 #refs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A caller does not need to start an upload before the interval. Uploads
run on the registration's interval and at the final sync.

The final sync still waits for a background sync that is running. Its
test now starts that sync from a one-second interval.

Ticket: Positronic-Robotics/internal#1637 #refs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The stop's 60 s join can return while a capped background sync still
runs.

Ticket: Positronic-Robotics/internal#1637 #refs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@v-positronic v-positronic changed the title Cap the total upload rate of a mirror, and sync uploads on request Cap the total upload rate of a mirror Sep 24, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 48788443a7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread pos3/__init__.py
An error exit with every upload at sync_on_error=False waited on the sync
lock for a sync of nothing. It now returns before the lock, so the
caller's exception is not held behind a background upload.

Ticket: Positronic-Robotics/internal#1637 #refs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c459e9744

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread pos3/rate_limit.py Outdated
A NaN rate passed the positive check and failed later in time.sleep.
An infinite rate stays valid and paces nothing.

Ticket: Positronic-Robotics/internal#1596 #refs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@v-positronic
v-positronic merged commit e7651c8 into main Sep 25, 2026
4 checks passed
@v-positronic
v-positronic deleted the upload-rate-cap branch September 25, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant