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

Use UnsafeMutablePointer for handling z_stream #12

Merged
merged 2 commits into from
Feb 26, 2025

Conversation

Cyberbeni
Copy link
Contributor

@Cyberbeni Cyberbeni commented Feb 26, 2025

deflateEnd/inflateEnd returned Z_STREAM_ERROR, when passing &self.stream into them, they returned Z_OK but AFAIK it is not guaranteed in Swift that C structs won't get moved( https://developer.apple.com/videos/play/wwdc2016/720/?time=1085 ), using UnsafeMutablePointer solves this.

fixes hummingbird-project/hummingbird-compression#33

@adam-fowler
Copy link
Owner

Have you verified this makes a difference?

@Cyberbeni
Copy link
Contributor Author

Yes, I added it to my Package.swift and did the same test as before (serve a basic site with a couple <100kB js/css files and force reload the page a lot of times).

@Cyberbeni
Copy link
Contributor Author

On macOS, I modified this example for testing: https://github.com/sliemeobn/elementary-htmx/tree/main/Examples/HummingbirdDemo

Still some minor leaks but not related to Zlib.

@Cyberbeni
Copy link
Contributor Author

image

@Cyberbeni
Copy link
Contributor Author

(The project needs to be built with swift cli, building Package.swift from Xcode doesn't work with Instruments)

Copy link
Owner

@adam-fowler adam-fowler left a comment

Choose a reason for hiding this comment

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

Thanks for this. All looks good. I verified it works as well. One very minor thing

@@ -138,7 +138,7 @@ public struct ZlibConfiguration: Sendable {

/// Compressor using Zlib
public final class ZlibCompressor {
var stream: z_stream
var stream: UnsafeMutablePointer<z_stream>
Copy link
Owner

Choose a reason for hiding this comment

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

Can we add a comment detailing why we need to use an UnsafeMutablePointer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, added comment to both places.

Copy link
Owner

Choose a reason for hiding this comment

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

Thanks. This is great. It might be worthwhile pointing this out in the swift-nio-extras repository. They have some request/response compression channel handlers use Zlib in a similar manner.

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 was already on the fence but then Apple decided that they want to play both sides (donating to Trump's inauguration, returning to advertising on X versus https://www.swift.org/diversity/ ), so I decided to not do any free QA for them unless absolutely necessary.

Copy link
Owner

Choose a reason for hiding this comment

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

Fair enough

@adam-fowler adam-fowler merged commit f801454 into adam-fowler:main Feb 26, 2025
5 checks passed
@adam-fowler
Copy link
Owner

@Cyberbeni Was thinking last night we could have just got rid of the copy of the z_stream in the deinit and that would have been enough to fix the memory leak.

@Cyberbeni
Copy link
Contributor Author

Yes but it could just as easily break again with just that change.

For example turning the wrapper into a struct would mean that copying/moving that changes the memory address, so changes outside of this file could break/fix the behavior.

I don't know if there is any magic behavior in the Swift runtime that would move stuff around in the memory but using UnsafeMutablePointer also prevents that.

C wrapper locks have been historically implemented the same way: https://github.com/apple/swift-metrics/blob/main/Sources/CoreMetrics/Locks.swift

@adam-fowler
Copy link
Owner

Its stored in a reference type so it is never copied

@Cyberbeni
Copy link
Contributor Author

https://github.com/swiftlang/swift/blob/af3e7e765549c0397288e60983c96d81639287ed/stdlib/public/core/UnsafePointer.swift#L197-L205

According to this, you shouldn't access implicitly created unsafe pointers (such as passing &stream to the C function) after the function call finished. So even though just removing the copy in deinit seemingly fixes the issue, there is no guarantee that future Swift versions won't work differently or that the current Swift version doesn't have any hidden edge cases when that memory address changes at runtime.

@Cyberbeni
Copy link
Contributor Author

For example just like how Hasher uses a random seed every time your app starts to prevent people from saving the hash (because if the hashing algorithm changes in a future version, that would change the hash of the same data anyways), Swift could generate a new virtual memory address every time there is an implicit unsafe pointer created and fatalError when that pointer is accessed after it's not supposed to be accessed.

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.

Memory leak
2 participants