Skip to content

Time-box runs, add rolling top-up windows, and skip provably empty checks - #8

Open
ryanwjackson wants to merge 3 commits into
mainfrom
timeout-window-and-topups
Open

ryanwjackson wants to merge 3 commits into
mainfrom
timeout-window-and-topups

Conversation

@ryanwjackson

Copy link
Copy Markdown
Contributor

Large trees make every run pay a full destination re-listing, and the platform timeout can kill rclone mid-upload. This changes how each execution spends its window.

What changed

Time-boxingtimeoutMinutes now reaches the container as COPY_TIMEOUT_MINUTES, and rclone runs with --max-duration (timeout minus a margin: a quarter of the timeout, capped at 15 minutes) and --cutoff-mode soft, so transfers in flight finish cleanly instead of being killed by replicaTimeout. rclone exit code 10 — the window closed with work remaining — is logged as transfer_window_exhausted and reported as success: the schedule is the resume mechanism, and a non-zero exit would fire an immediate unscheduled retry through replicaRetryLimit: 1 and still mark the execution failed.

Rolling top-up window — new optional source.topUpMaxAge field (e.g. "48h"): copy only files modified within the window, with --no-traverse so the destination tree is never listed. On a converged large tree this turns a full-walk run into a source-walk-plus-candidate-checks run. Mutually exclusive with includePaths and modifiedOnOrAfter; the fixed cutoff deliberately does not get --no-traverse, which backfires on wide selections. Size the window to at least twice the schedule interval and keep a periodic job without it as the full reconcile pass (see the example.jsonc comment).

Empty-destination fast path — one lsf probe before a whole-tree copy; if the destination is provably empty, --no-check-dest --retries 1 skips per-file existence checks for that run. Any listed content or any probe error selects the normal comparing path, so partially populated destinations are never at risk of duplicates.

Throttling — every run sends Microsoft's documented ISV user-agent (ISV|rclone.org|rclone/<version>) so SharePoint throttles the service less aggressively.

Compatibility

  • Deployments predating these variables: pull treats the new fields as optional; apply backfills them.
  • Parameter files written before topUpMaxAge existed keep deploying (safe access in the template); the portal form emits the field as empty.
  • infra/main.json regenerated with bicep 0.46.1 (generator stamps in the diff are from the version bump).

Testing

copyctl.py validate, tests/config_test.py, tests/uidefinition_test.py, tests/template_test.py, tests/transfer_test.sh, and shellcheck all pass locally; bicep builds clean. Not covered: a real Azure execution — the probe, time-box, and exit-10 paths are exercised against the fake rclone only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QtQWZLnq1WDQ79k8Bn5xbo

ryanwjackson and others added 3 commits August 3, 2026 18:25
…ecks

Three changes to how a copy execution spends its window:

- The job's timeoutMinutes now reaches the container as
  COPY_TIMEOUT_MINUTES, and rclone gets --max-duration with a margin
  (a quarter of the timeout, at most 15 minutes) and --cutoff-mode soft,
  so in-flight uploads finish instead of being killed mid-chunk by the
  platform's replicaTimeout. rclone's exit code 10 (window closed with
  work remaining) is recorded as transfer_window_exhausted and reported
  as success: the schedule is the resume mechanism, and a non-zero exit
  would trigger an immediate unscheduled retry via replicaRetryLimit.

- A new optional source.topUpMaxAge job field ("48h") selects only
  recently modified files and adds --no-traverse, so a frequent schedule
  stops re-listing the whole destination tree once an initial full copy
  exists. It cannot be combined with includePaths or modifiedOnOrAfter,
  and the fixed modifiedOnOrAfter cutoff deliberately does not get
  --no-traverse, which is a net loss on wide selections.

- Before a whole-tree copy, one listing probes whether the destination
  is empty; if it provably is, --no-check-dest --retries 1 skips
  per-file existence checks for that run. Any content or any probe
  error selects the normal comparing path.

Every run now also sends Microsoft's documented ISV user-agent so
SharePoint throttles the copy less aggressively.

Older deployments lack the new variables: pull treats them as optional
and apply backfills them. infra/main.json regenerated (bicep 0.46.1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtQWZLnq1WDQ79k8Bn5xbo
Reject zero and leading-zero top-up magnitudes to match copyctl.py,
require COPY_TIMEOUT_MINUTES to be at least 5 so --max-duration can
never compute to rclone's disabled value, and make the top-up test
exercise the probe-skip guard by faking an empty destination.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jcdrAtd4YsuMCsDWjuFSA
The shell rejects leading zeros in SOURCE_TOP_UP_MAX_AGE, so copyctl now
does too instead of deploying a value every run would then refuse. Job
files written before topUpMaxAge existed keep validating, the same way
the template tolerates old parameter files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCsfMHP5zKkxTiQf9rFpyn
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