SIGN IN SIGN UP

Implement PieceNotifier and improve download scheduling logic (#1941)

* feat(task): wait for in-flight pieces before scheduling download

- Before scheduling, check if the task is prefetched and wait for
  in-flight pieces to finish writing to local storage
- Poll local storage at `DEFAULT_WAIT_FOR_PIECE_FINISHED_INTERVAL`
  intervals up to `DEFAULT_WAIT_FOR_IN_FLIGHT_PIECE_TIMEOUT` (500ms)
- Stop early if all interested pieces finish or none remain in-flight
- Reduces scheduler stalls caused by missing candidate parents during
  concurrent prefetch downloads

Signed-off-by: Gaius <gaius.qi@gmail.com>

* refactor: move prefetch task started log to after task initiation

- Move "prefetch task started" log to just before the actual download
  begins
- Ensures the log reflects task initiation more accurately
- Upgrade instrument log level from `debug` to default (`info`) for
  proxy handlers

Signed-off-by: Gaius <gaius.qi@gmail.com>

* style(grpc): downgrade "task is already prefetched" log to debug level

Changes `info!` to `debug!` for "task is already prefetched" in both
download and upload gRPC handlers, reducing log noise for a
normal/expected condition.

Signed-off-by: Gaius <gaius.qi@gmail.com>

* refactor: remove redundant `level = "debug"` from instrument macros

- Replace `level = "debug"` with default in all `#[instrument]` macros
- Add missing `#[instrument(skip_all)]` to `HuggingFace` and
  `ModelScope` backends
- Downgrade a 416-retry log from `info!` to `debug!` in HTTP backend
- Remove unused `info` import in `http.rs

Signed-off-by: Gaius <gaius.qi@gmail.com>

* feat(storage): add PieceNotifier for event-driven piece completion

- Add `PieceNotifier` struct using `DashMap<String, Arc<Notify>>` to
  enable event-driven piece completion signaling
- Provide `register`, `get`, and `remove_and_notify` methods to manage
  per-piece notifiers across concurrent downloads
- Add `DEFAULT_WAIT_FOR_PIECE_FINISHED_FALLBACK_INTERVAL` (1s) as a
  safety re-check interval in case notifications are missed
- Attach `piece_notifier` field to `Storage` struct
- Demote several `#[instrument]` spans to `level = "debug"` to reduce
  trace noise; remove `#[instrument]` from non-async constructor methods

Signed-off-by: Gaius <gaius.qi@gmail.com>

* refactor(storage): extract PieceNotifier and replace polling with notify

- Move `PieceNotifier` into its own `piece_notifier` module.
- Replace interval-based polling loops with `Notify`-driven wakeups,
  falling back to a timeout interval.
- Register notifier before piece metadata creation; call
  `remove_and_notify` on finish, failure, or error paths.
- Expose `in_flight_piece_notifier()` for external waiters.
- Add tests covering notify-driven wake, fail-on-failure, and
  fast-fail for stale (orphaned) piece metadata.

Signed-off-by: Gaius <gaius.qi@gmail.com>

* refactor(storage): replace polling with notifier in wait_for_cache_piece

- Replace interval-based polling with `Notify`-based notifier in
  `wait_for_cache_piece_finished`
- Register notifier on `download_cache_piece_started`; notify on
  finish, fail, or timeout
- Add fallback sleep interval and double-check loop for race-free
  `None` notifier case
- Notify and clean up on `download_cache_piece_failed` to unblock
  waiters
- Elevate several `#[instrument(level = "debug")]` to default (info)
  level

Signed-off-by: Gaius <gaius.qi@gmail.com>

* refactor: move DEFAULT_WAIT_FOR_PIECE_FINISHED_INTERVAL to dfdaemon_upload

- Moves `DEFAULT_WAIT_FOR_PIECE_FINISHED_INTERVAL` from
  `dragonfly-client-storage` to `dfdaemon_upload` where it's actually
  used
- Simplifies call sites by removing the `dragonfly_client_storage::`
  prefix

Signed-off-by: Gaius <gaius.qi@gmail.com>

* style: remove redundant inline comments from tests

- Remove inline comments that restate obvious code intent
- Affected files: `lib.rs`, `piece_notifier.rs`, and `task.rs` test
  modules
- Comments describing setup steps, assertions, and no-ops are dropped

Signed-off-by: Gaius <gaius.qi@gmail.com>

* fix(storage): prevent failed download from erasing finished piece

- Guard `download_piece_failed` to skip deletion if piece is already
  finished
- Guard `wait_for_piece_finished_failed` with the same check
- Prevents a late/duplicate downloader from erasing a piece completed by
  the winner
- Add unit tests in both `metadata` and `storage` layers to cover the
  fix

Signed-off-by: Gaius <gaius.qi@gmail.com>

* fix(piece): move scopeguard after finished piece check

- Register scopeguard only when piece is not already finished
- Avoids calling `download_piece_failed` on successfully cached pieces
- Removes need for manual `ScopeGuard::into_inner` on early return

Signed-off-by: Gaius <gaius.qi@gmail.com>

* feat(task): add direct source download for seed peer range tasks

- Rename `download_and_ensure_announcement` to `download_with_scheduler`
- For seed peer range tasks where range length ≤ max piece length,
  bypass the scheduler and download directly from source
- Return early after direct source download, skipping scheduler logic

Signed-off-by: Gaius <gaius.qi@gmail.com>

* fix(metric): change slow task detection from Level1 to Level2 threshold

- Change slow task detection from `Level1` to `Level2` size threshold
  for both upload and download tasks
- Add `#[instrument(skip_all)]` to upload/download finished metric
  functions
- Fix trailing comma in `warn!` macro call
- Update `.gitignore` to exclude `.claude` and `.claude-analysis` files

Signed-off-by: Gaius <gaius.qi@gmail.com>

* feat(storage): use atomic claim to prevent duplicate piece downloads

- Replace `register` with atomic `claim` returning `Owner` or `InFlight`
- `Owner` callers proceed to download; `InFlight` callers wait on the
  existing notifier and retry in a loop
- Add double-check after winning the claim to skip download if piece
  already finished
- Apply the same pattern to all four piece download entry points
- Add concurrency test asserting exactly one downloader wins per piece

Signed-off-by: Gaius <gaius.qi@gmail.com>

* fix(fs): run fallocate in spawn_blocking to avoid blocking async runtime

- Move `fallocate` syscall into `spawn_blocking` to prevent blocking
  the async runtime
- Clone the file handle before passing it into the blocking task
- Map `JoinError` to `io::Error` after awaiting the task

Signed-off-by: Gaius <gaius.qi@gmail.com>

* fix(fs): simplify fallocate return with double question mark operator

- Replace explicit `return` with implicit return using `??` operator
- Collapse `map_err(...)?` + `return` into a single `??` expression
- Remove redundant `#[cfg(not(target_os = "linux"))]` attribute before
  `Ok(())

Signed-off-by: Gaius <gaius.qi@gmail.com>

---------

Signed-off-by: Gaius <gaius.qi@gmail.com>
G
Gaius committed
11054edfa6dc19c3aa0be1169f2dc2b4e0d53d64
Parent: 00b3e2b
Committed by GitHub <noreply@github.com> on 7/21/2026, 2:45:33 PM