Skip to content

Refactor SKManagedStream: replace parent/child chain with SKData snapshot for duplicate/fork#3588

Closed
Copilot wants to merge 4 commits into
mainfrom
copilot/extract-skmanagedstream-changes
Closed

Refactor SKManagedStream: replace parent/child chain with SKData snapshot for duplicate/fork#3588
Copilot wants to merge 4 commits into
mainfrom
copilot/extract-skmanagedstream-changes

Conversation

Copilot AI commented Mar 28, 2026

Copy link
Copy Markdown
Contributor
  • Refactor SKAbstractManagedStream: add private protected (bool owns, bool weak) constructor
  • Refactor SKManagedStream duplicate/fork semantics with SKData snapshot
  • Update SKManagedStreamTest.cs for new independent stream behavior
  • Remove unused private protected SKAbstractManagedStream(bool owns, bool weak) constructor — dead code since OnDuplicate/OnFork now return native handles directly
  • Remove unused private SKManagedStream(Stream, bool, bool weak) constructor — never called
  • Restore SKAbstractManagedStream(bool owns) to call base directly
  • Build and test verified (33 passed, 6 skipped on Linux)

⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.

Copilot AI and others added 2 commits March 28, 2026 16:30
…shot for duplicate/fork

Agent-Logs-Url: https://github.com/mono/SkiaSharp/sessions/923fa277-732c-47b8-9b41-f421fb165b9d

Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Copilot AI changed the title [WIP] Extract changes to skmanagedstream and related tests Refactor SKManagedStream: replace parent/child chain with SKData snapshot for duplicate/fork Mar 28, 2026
Copilot AI requested a review from mattleibow March 28, 2026 16:37
…onstructor

Agent-Logs-Url: https://github.com/mono/SkiaSharp/sessions/6018f013-525a-4a42-bec0-37873a909c77

Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors SKManagedStream’s duplicate/fork behavior to produce independent streams by snapshotting the underlying seekable .NET stream into a shared SKData, aligning the managed implementation more closely with “independent stream” semantics.

Changes:

  • Updated SKManagedStream to lazily create and reuse a SKData snapshot for Duplicate/Fork, removing the previous parent/child WeakReference chain behavior.
  • Updated managed-stream duplication/forking tests to validate that original and duplicated/forked streams remain readable and independent.
  • Adjusted disposal-related test coverage to reflect the new independent-stream lifecycle behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
binding/SkiaSharp/SKManagedStream.cs Implements Duplicate/Fork by creating a shared SKData snapshot and returning native memory streams backed by that snapshot.
tests/Tests/SkiaSharp/SKManagedStreamTest.cs Updates tests to validate the new semantics: duplicates/forks are readable independently and multiple duplicates are supported.

Comment on lines +180 to 197
private SKData GetOrCreateSnapshot ()
{
VerifyOriginal ();
if (snapshotData != null)
return snapshotData;

if (!stream.CanSeek)
return IntPtr.Zero;

var newStream = new SKManagedStream (stream, disposeStream);
newStream.parent = new WeakReference (this);

wasCopied = true;
disposeStream = false;
child = new WeakReference (newStream);

stream.Position = 0;
return null;

var pos = stream.Position;
try {
stream.Position = 0;
snapshotData = SKData.Create (stream, stream.Length);
} finally {
stream.Position = pos;
}

return newStream.Handle;
return snapshotData;
}

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

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

GetOrCreateSnapshot() reads the entire managed stream into an SKData snapshot (native memory) on first Duplicate/Fork and keeps it alive for the lifetime of the SKManagedStream. This changes Duplicate/Fork from O(1) to O(stream.Length) memory/time and can retain very large buffers even if only a single duplicate was needed. Consider limiting snapshot creation (e.g., only when Skia requests duplicate, and optionally releasing the snapshot after the last duplicate/fork is created or providing a size/feature guard), or documenting this clearly as a potentially expensive operation.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +17
// via SKData (ref-counted), so multiple duplicates share one copy with
// zero additional managed allocations per duplicate.

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

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

The field comment says duplicates share one copy with “zero additional managed allocations per duplicate”, but each duplicate still creates a managed SKStream wrapper object. If the intent is to say there are no additional byte-buffer allocations in managed memory after the initial snapshot, consider rewording to avoid overpromising.

Suggested change
// via SKData (ref-counted), so multiple duplicates share one copy with
// zero additional managed allocations per duplicate.
// via SKData (ref-counted), so multiple duplicates share one native
// byte buffer with no additional managed byte-buffer allocations per duplicate.

Copilot uses AI. Check for mistakes.
@mattleibow

Copy link
Copy Markdown
Contributor

Closing in favor of PR #3589, which has the same SKData snapshot implementation but with better test coverage:

  • Keeps the DuplicateStreamIsCollected GC test (this PR removed it)
  • Adds a separate DuplicateStreamIsDisposed test
  • Correctly renames DotNetStreamIsNotClosedPrematurelyDotNetStreamIsClosedWhenSKManagedStreamIsDisposed with reordered assertions that document the new lifecycle behavior
  • Removes unnecessary SkipOnNonWindows() from 6 tests (the old skip existed because VerifyOriginal() threw inside native delegates — the new implementation never throws in native delegates)
  • Adds NonSeekableStreamDuplicateAndForkReturnNull test
  • Fixes misleading comment about zero allocations per duplicate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants