-
Notifications
You must be signed in to change notification settings - Fork 125
Fix thread leak in FileDownloadDelegate
#614
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
Fix thread leak in FileDownloadDelegate
#614
Conversation
c6a1a3d
to
ac67b22
Compare
API breakage failure is a false positive
The change in question can be simplified to: struct Foo {
// old init
init(bar: Int = 0) {}
// new inits
init() {}
init(bar: Int) {}
} replacing a default argument with two |
break | ||
case .owned(let pool): | ||
// if the delegate is unused we still need to shutdown the thread pool | ||
try! pool.syncShutdownGracefully() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we shouldn't destroy this in deinit
, you don't even have a real guarantee that deinit
isn't running in a thread owned by the pool (which would make it deadlock).
I'd suggest to add a lifecycle hook (shutdown or so) to the delegate and have AHC invoke that. That new shutdown method can have an empty default implementation to not break the API.
Separately, I think AHC should provide the thread pool or else we spin up/down a thread per download and that kinda makes it pointless to do asynchronous I/O if we need a new thread each time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a lifecycle hook compatible with AHC providing the thread pool?
I'm inclined to say that if the user doesn't provide a thread pool we should just use a global static. Users can override the thread pool for testing if they need to shut stuff down, but the default case is that they don't care and the threads can be cleaned up at termination.
How do you feel about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually you could add two delegate methods (with default empty impls):
provideThreadPool(ioPool)
where AHC provides a running thread pool to the delegateshutdown()
where AHC tells the delegate that it its job is done
After that, we can change the default here and have the delegate not even have the option to accept an io pool from the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If AHC provides the default shared thread pool we don't need the shutdown()
lifecycle hook because the delegate doesn't need to shutdown the thread pool anymore but the AHC client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a shared file IO thread pool to the HTTPClient
which is passed to the delegate through a call to provideSharedThreadPool(fileIOPool:)
1afc151
554c0d5
to
1afc151
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome, thank you!
6586cf0
to
ac0b9f7
Compare
I have changed the implementation because we would otherwise only ever use one thread for all file IO. This is not good because file IO can block the thread and there is no easy way of making it non-blocking. We therefore initialize the thread pool with |
…/async-http-client into dn-fix-file-download-thread-leak
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good, thanks, but we'll want some tests of the functionality I think.
|
||
init(eventLoop: EventLoop, logger: Logger) { | ||
public var fileIOThreadPool: NIOThreadPool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that we actually don't need to make this public. @Lukasa any objections to me making it internal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in c3c68b2
… all delegates for a given HTTPClient
Fixes #364
Modification
Result
we no longer leak one thread per download