Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't allocate when adding attachments. #616

Merged
merged 6 commits into from
Nov 4, 2020

Conversation

JimBobSquarePants
Copy link
Contributor

@JimBobSquarePants JimBobSquarePants commented Nov 3, 2020

Hi, I don't know if you're looking for this kind of PR but I spotted the allocation when F12ing through the methods in Visual Studio.

A quick search for new byte[ indicates that there are quite a few places in the library where the same pattern could be used e.g. FilteredStream.Read. Unfortunately I cannot currently dedicate the time to replace each instance so I thought I would introduce an example fix for this instance.

@JimBobSquarePants
Copy link
Contributor Author

Failing test TestReferenceByContentId does not seem to be related the change and fails with current master on my machine.

Message: 
      Contains failed.
      Expected: True
      But was:  False
    
  Stack Trace: 
    MultipartRelatedTests.TestReferenceByContentId() line 151

Indecently TestDocumentRoot fails on my machine also with the message.

  Message: 
      Initial Root
      Expected: <X-MimeKit-Warning: Do NOT use ToString() to serialize entities! Use one of the WriteTo() methods instead!
    Content-Type: text/html; charset=utf-8
    Content-Id: <7F64657G3CU4.1KUI3FL529CE3@Mjölnir>
    
    This is the html body...>
      But was:  <X-MimeKit-Warning: Do NOT use ToString() to serialize entities! Use one of the WriteTo() methods instead!
    Content-Type: image/gif
    Content-Disposition: inline; filename=empty.gif
    Content-Id: <Z3H2657G3CU4.QIGWC03L02CU3@Mjölnir>
    
    >
    
  Stack Trace: 
    MultipartRelatedTests.TestDocumentRoot() line 70

filter.Filter (buf, 0, nread, out index, out length);
content.Write (buf, 0, nread);
}

filter.Flush (buf, 0, 0, out index, out length);

ArrayPool<byte>.Shared.Return (buf);

Choose a reason for hiding this comment

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

I'd usually recommend wrapping the above code (while loop and flush) inside a try block and then putting the return inside a finally to ensure that it's returned despite potential exceptions. It'll get collected eventually but it's the pattern I've seen recommended.

Copy link
Contributor Author

@JimBobSquarePants JimBobSquarePants Nov 3, 2020

Choose a reason for hiding this comment

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

Yeah it's all managed memory so the GC will collect the buffer eventually, that's why I didn't bother since the exception would go unhandled and try.. finally.. gives a false sense of security.

I could add the pattern though if that's what everyone wants.

Choose a reason for hiding this comment

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

I must admit, I've not explored the usage of this and whether exceptions may be caught higher up. I'd assumed that might be the case and if so, there's a slight benefit to putting it back in the pool cleanly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add the pattern then (I haven't explored either tbh.)

@jstedfast
Copy link
Owner

I've fixed that unit test.

Does ArrayPool<byte>.Shared.Rent (BufLength); sometimes return buffers of a length > than the size requested? I was wondering why use this constant if not.

@jstedfast
Copy link
Owner

FWIW, I originally did not use System.Buffers because it was not available on all of the platforms that MimeKit originally supported.

I think that all of those .NET platforms have since been dropped, but I will need to verify.

If System.Buffers is available on all supported platforms, then I will be able to drop https://github.com/jstedfast/MimeKit/blob/master/MimeKit/Utils/BufferPool.cs (effectively the same idea).

@JimBobSquarePants
Copy link
Contributor Author

Does ArrayPool.Shared.Rent (BufLength); sometimes return buffers of a length > than the size requested? I was wondering why use this constant if not.

@jstedfast Yes, the length can be greater so when working with buffers directly so you should ensure that you do not rely on that property. Ideally you'd wrap the pool and work directly with span slices only falling back to the array when you need it.

@jstedfast jstedfast merged commit fd604e7 into jstedfast:master Nov 4, 2020
@jstedfast
Copy link
Owner

I'll work on updating more areas of the code to use ArrayPool.

@JimBobSquarePants
Copy link
Contributor Author

Might be an idea to define some sort of implementation similar to IMemoryOwner<T> to simplify pooling and length handling.

@jstedfast
Copy link
Owner

Do you have an example of IMemoryOwner being used? Is the idea just to replace the need for the try/finally syntax and replacing it with a using block?

@jstedfast
Copy link
Owner

BTW, just spotted this: Content-Id: <Z3H2657G3CU4.QIGWC03L02CU3@Mjölnir>

Is that your machine's hostname? Looks like I might need to encode the hostname if so. I sure hope it's that and not some sort of memory corruption.

@JimBobSquarePants
Copy link
Contributor Author

Yeah Mjölnir 😄

@jstedfast
Copy link
Owner

Cool, cool :)

I just fixed GenerateMessageId() to encode the hostname if it is international.

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.

4 participants