Skip to content

Rich MIME transfer engine conflates liveness, completeness, and timeout policy #9

Description

@c-wri

Observed

Live rich-content qualification under #8 failed on Firefox at the first Browser -> Writer capture step.

Build/runtime:

  • main at 87b4822829aea8d8285867b6378282976e47c670
  • ClipReg 0.2.0-beta.1+g87b4822829ae
  • service remained active/running, NRestarts=0
  • F8 did not exist before or after the failed grab

Transaction evidence:

phase=grab-safe-copy
phase=grab-profile-copy
phase=grab-capture-result
...
phase=complete status=error code=110 duration_ms=3579

The generic Ctrl+Insert probe timed out for Firefox, then the explicit Firefox Ctrl+C fallback produced a new external clipboard generation and advanced to grab-capture-result. Failure therefore occurred while materializing the newly advertised MIME object, not while invoking application Copy.

Root design defect

The current external-offer materializer treats a multi-MIME clipboard object as a serial list of blocking byte streams:

  1. request one MIME;
  2. wait up to the fixed transfer_timeout_ms for EOF;
  3. only after completion start the next MIME;
  4. on the first receive error/timeout, free the entire partially captured item and return the error.

read_pipe_capped() uses one absolute deadline created at transfer start. Receiving bytes does not extend that deadline. Consequently:

  • one stalled representation creates head-of-line blocking and prevents later good representations from being requested;
  • a healthy large/slow representation that continuously makes progress can be falsely failed merely for taking longer than the fixed wall-clock timeout;
  • one MIME failure destroys already completed sibling representations;
  • current logs do not identify the advertised MIME manifest, which representation failed, which completed, byte counts, or what good data was discarded;
  • the timeout is per representation rather than a coherent whole-capture bound, so worst-case operation duration scales with advertised MIME count.

The outgoing path has the same architectural smell: source_send() writes an entire requested representation synchronously inside the Wayland callback using the same fixed absolute timeout, which can block Wayland dispatch for a large/slow consumer.

This conflicts with docs/architecture.md: timeouts bound waiting; observable protocol/data progress is the synchronization authority.

Violated invariants

  • Independent MIME representations must not head-of-line block one another.
  • Data progress and transfer completion are distinct: bytes arriving prove liveness; EOF proves protocol completion.
  • A healthy transfer may exceed a nominal duration while making bounded progress; inactivity and whole-transaction bounds are separate concerns.
  • A failed grab never overwrites the prior register.
  • Partial success must never be silently presented as a complete register.
  • Any discarded representation must be explainable without logging clipboard payload bytes.
  • Wayland callbacks must not perform potentially long blocking clipboard I/O.

Target transfer model

Use one nonblocking event-driven transfer manager inside the existing user daemon. Keep Wayland, command socket, transaction state, incoming MIME pipes, outgoing consumer pipes, and timers under one reactor; do not add another daemon/process merely for transfer I/O.

For each representation track at least:

  • sanitized MIME name and manifest index/count;
  • requested time;
  • time to first progress;
  • bytes transferred;
  • last-progress time;
  • EOF/completion;
  • explicit failure reason (no-first-progress, no-progress, payload-cap, selection-changed, FD/protocol error, whole-capture bound, etc.).

Incoming representations should be independently materialized with bounded concurrency so one bad MIME does not prevent siblings from progressing. Outgoing source_send callbacks should enqueue nonblocking writes and return immediately rather than draining the consumer FD synchronously.

Use distinct bounds for:

  • first progress;
  • idle/no-progress after transfer has begun;
  • whole capture/transaction lifetime;
  • total payload bytes.

Do not add user-facing tuning knobs until live/synthetic evidence demonstrates which bounds need configuration.

Failure policy

For the current register model, remain strict by default: if any representation classified as required/persistable fails, continue enough sibling work to produce complete diagnostics, preserve the prior register, and report object-level failure. Do not silently degrade to plain text or treat timeout as an accidental MIME filter.

If live evidence proves that a class of advertised MIME is intentionally non-persistable/non-materializable, make that an explicit documented policy with regression coverage rather than skipping whatever happens to fail.

Required observability

Permanent metadata-only diagnostics at the materialization boundary should make a failure self-explaining, for example:

snapshot-offer mimes=7 generation=143
snapshot-mime index=2/7 mime="text/html" state=complete bytes=3917 duration_ms=8
snapshot-mime index=4/7 mime="..." state=failed reason=no-progress bytes=32768
snapshot-abort advertised=7 completed=6 failed=1 completed_bytes=48291 discarded_representations=6 discarded_bytes=48291

Never log clipboard payload bytes.

Acceptance

  • Identify the exact Firefox representation and transfer state that triggered the original failure.
  • Add lowest-layer synthetic regressions for: independent sibling progress, zero-byte stall, partial-progress stall, a transfer that takes longer than the old 3 s bound while continually progressing, total payload cap, whole-capture bound, selection generation change, old-register preservation, and atomic all-success commit.
  • Ensure outgoing large/slow consumer transfers no longer block the Wayland event loop.
  • Browser rich capture under Qualify rich MIME capture and paste across browser and LibreOffice #8 succeeds with the rich MIME representations Firefox actually completes plus text fallback, while preserving the unrelated clipboard.
  • Re-run Writer/Calc rich qualification before v0.2.0-rc.1 promotion.

This blocks v0.2.0-rc.1 through #8.

Metadata

Metadata

Assignees

Labels

area:runtimeNative daemon, protocol, transaction, or persistence behaviorbugSomething is not workingpriority:blockerBlocks the next release promotionrelease:0.2Targets the v0.2.0 line

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions